many lint fixes and optimizations (#17)
* util/kubernetes: drop stale files * debug/log/kubernetes: drop stale files * util/scope: remove stale files * util/mdns: drop stale files * lint fixes Signed-off-by: Vasiliy Tolstov <v.tolstov@unistack.org>
This commit was merged in pull request #17.
	This commit is contained in:
		| @@ -1,121 +0,0 @@ | |||||||
| // Package api provides an http-rpc handler which provides the entire http request over rpc |  | ||||||
| package api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
|  |  | ||||||
| 	goapi "github.com/unistack-org/micro/v3/api" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/handler" |  | ||||||
| 	api "github.com/unistack-org/micro/v3/api/proto" |  | ||||||
| 	"github.com/unistack-org/micro/v3/client" |  | ||||||
| 	"github.com/unistack-org/micro/v3/errors" |  | ||||||
| 	"github.com/unistack-org/micro/v3/util/ctx" |  | ||||||
| 	"github.com/unistack-org/micro/v3/util/router" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type apiHandler struct { |  | ||||||
| 	opts handler.Options |  | ||||||
| 	s    *goapi.Service |  | ||||||
| } |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	Handler = "api" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // API handler is the default handler which takes api.Request and returns api.Response |  | ||||||
| func (a *apiHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	bsize := handler.DefaultMaxRecvSize |  | ||||||
| 	if a.opts.MaxRecvSize > 0 { |  | ||||||
| 		bsize = a.opts.MaxRecvSize |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	r.Body = http.MaxBytesReader(w, r.Body, bsize) |  | ||||||
| 	request, err := requestToProto(r) |  | ||||||
| 	if err != nil { |  | ||||||
| 		er := errors.InternalServerError("go.micro.api", err.Error()) |  | ||||||
| 		w.Header().Set("Content-Type", "application/json") |  | ||||||
| 		w.WriteHeader(500) |  | ||||||
| 		w.Write([]byte(er.Error())) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var service *goapi.Service |  | ||||||
|  |  | ||||||
| 	if a.s != nil { |  | ||||||
| 		// we were given the service |  | ||||||
| 		service = a.s |  | ||||||
| 	} else if a.opts.Router != nil { |  | ||||||
| 		// try get service from router |  | ||||||
| 		s, err := a.opts.Router.Route(r) |  | ||||||
| 		if err != nil { |  | ||||||
| 			er := errors.InternalServerError("go.micro.api", err.Error()) |  | ||||||
| 			w.Header().Set("Content-Type", "application/json") |  | ||||||
| 			w.WriteHeader(500) |  | ||||||
| 			w.Write([]byte(er.Error())) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		service = s |  | ||||||
| 	} else { |  | ||||||
| 		// we have no way of routing the request |  | ||||||
| 		er := errors.InternalServerError("go.micro.api", "no route found") |  | ||||||
| 		w.Header().Set("Content-Type", "application/json") |  | ||||||
| 		w.WriteHeader(500) |  | ||||||
| 		w.Write([]byte(er.Error())) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// create request and response |  | ||||||
| 	c := a.opts.Client |  | ||||||
| 	req := c.NewRequest(service.Name, service.Endpoint.Name, request) |  | ||||||
| 	rsp := &api.Response{} |  | ||||||
|  |  | ||||||
| 	// create the context from headers |  | ||||||
| 	cx := ctx.FromRequest(r) |  | ||||||
|  |  | ||||||
| 	if err := c.Call(cx, req, rsp, client.WithRouter(router.New(service.Services))); err != nil { |  | ||||||
| 		w.Header().Set("Content-Type", "application/json") |  | ||||||
| 		ce := errors.Parse(err.Error()) |  | ||||||
| 		switch ce.Code { |  | ||||||
| 		case 0: |  | ||||||
| 			w.WriteHeader(500) |  | ||||||
| 		default: |  | ||||||
| 			w.WriteHeader(int(ce.Code)) |  | ||||||
| 		} |  | ||||||
| 		w.Write([]byte(ce.Error())) |  | ||||||
| 		return |  | ||||||
| 	} else if rsp.StatusCode == 0 { |  | ||||||
| 		rsp.StatusCode = http.StatusOK |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, header := range rsp.GetHeader() { |  | ||||||
| 		for _, val := range header.Values { |  | ||||||
| 			w.Header().Add(header.Key, val) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(w.Header().Get("Content-Type")) == 0 { |  | ||||||
| 		w.Header().Set("Content-Type", "application/json") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	w.WriteHeader(int(rsp.StatusCode)) |  | ||||||
| 	w.Write([]byte(rsp.Body)) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (a *apiHandler) String() string { |  | ||||||
| 	return "api" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewHandler(opts ...handler.Option) handler.Handler { |  | ||||||
| 	options := handler.NewOptions(opts...) |  | ||||||
| 	return &apiHandler{ |  | ||||||
| 		opts: options, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func WithService(s *goapi.Service, opts ...handler.Option) handler.Handler { |  | ||||||
| 	options := handler.NewOptions(opts...) |  | ||||||
| 	return &apiHandler{ |  | ||||||
| 		opts: options, |  | ||||||
| 		s:    s, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,109 +0,0 @@ | |||||||
| package api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"mime" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/oxtoacart/bpool" |  | ||||||
| 	api "github.com/unistack-org/micro/v3/api/proto" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	// need to calculate later to specify useful defaults |  | ||||||
| 	bufferPool = bpool.NewSizedBufferPool(1024, 8) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func requestToProto(r *http.Request) (*api.Request, error) { |  | ||||||
| 	if err := r.ParseForm(); err != nil { |  | ||||||
| 		return nil, fmt.Errorf("Error parsing form: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	req := &api.Request{ |  | ||||||
| 		Path:   r.URL.Path, |  | ||||||
| 		Method: r.Method, |  | ||||||
| 		Header: make(map[string]*api.Pair), |  | ||||||
| 		Get:    make(map[string]*api.Pair), |  | ||||||
| 		Post:   make(map[string]*api.Pair), |  | ||||||
| 		Url:    r.URL.String(), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ct, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) |  | ||||||
| 	if err != nil { |  | ||||||
| 		ct = "text/plain; charset=UTF-8" //default CT is text/plain |  | ||||||
| 		r.Header.Set("Content-Type", ct) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	//set the body: |  | ||||||
| 	if r.Body != nil { |  | ||||||
| 		switch ct { |  | ||||||
| 		case "application/x-www-form-urlencoded": |  | ||||||
| 			// expect form vals in Post data |  | ||||||
| 		default: |  | ||||||
| 			buf := bufferPool.Get() |  | ||||||
| 			defer bufferPool.Put(buf) |  | ||||||
| 			if _, err = buf.ReadFrom(r.Body); err != nil { |  | ||||||
| 				return nil, err |  | ||||||
| 			} |  | ||||||
| 			req.Body = buf.String() |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Set X-Forwarded-For if it does not exist |  | ||||||
| 	if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { |  | ||||||
| 		if prior, ok := r.Header["X-Forwarded-For"]; ok { |  | ||||||
| 			ip = strings.Join(prior, ", ") + ", " + ip |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// Set the header |  | ||||||
| 		req.Header["X-Forwarded-For"] = &api.Pair{ |  | ||||||
| 			Key:    "X-Forwarded-For", |  | ||||||
| 			Values: []string{ip}, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Host is stripped from net/http Headers so let's add it |  | ||||||
| 	req.Header["Host"] = &api.Pair{ |  | ||||||
| 		Key:    "Host", |  | ||||||
| 		Values: []string{r.Host}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Get data |  | ||||||
| 	for key, vals := range r.URL.Query() { |  | ||||||
| 		header, ok := req.Get[key] |  | ||||||
| 		if !ok { |  | ||||||
| 			header = &api.Pair{ |  | ||||||
| 				Key: key, |  | ||||||
| 			} |  | ||||||
| 			req.Get[key] = header |  | ||||||
| 		} |  | ||||||
| 		header.Values = vals |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Post data |  | ||||||
| 	for key, vals := range r.PostForm { |  | ||||||
| 		header, ok := req.Post[key] |  | ||||||
| 		if !ok { |  | ||||||
| 			header = &api.Pair{ |  | ||||||
| 				Key: key, |  | ||||||
| 			} |  | ||||||
| 			req.Post[key] = header |  | ||||||
| 		} |  | ||||||
| 		header.Values = vals |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for key, vals := range r.Header { |  | ||||||
| 		header, ok := req.Header[key] |  | ||||||
| 		if !ok { |  | ||||||
| 			header = &api.Pair{ |  | ||||||
| 				Key: key, |  | ||||||
| 			} |  | ||||||
| 			req.Header[key] = header |  | ||||||
| 		} |  | ||||||
| 		header.Values = vals |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return req, nil |  | ||||||
| } |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| package api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/url" |  | ||||||
| 	"testing" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestRequestToProto(t *testing.T) { |  | ||||||
| 	testData := []*http.Request{ |  | ||||||
| 		{ |  | ||||||
| 			Method: "GET", |  | ||||||
| 			Header: http.Header{ |  | ||||||
| 				"Header": []string{"test"}, |  | ||||||
| 			}, |  | ||||||
| 			URL: &url.URL{ |  | ||||||
| 				Scheme:   "http", |  | ||||||
| 				Host:     "localhost", |  | ||||||
| 				Path:     "/foo/bar", |  | ||||||
| 				RawQuery: "param1=value1", |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, d := range testData { |  | ||||||
| 		p, err := requestToProto(d) |  | ||||||
| 		if err != nil { |  | ||||||
| 			t.Fatal(err) |  | ||||||
| 		} |  | ||||||
| 		if p.Path != d.URL.Path { |  | ||||||
| 			t.Fatalf("Expected path %s got %s", d.URL.Path, p.Path) |  | ||||||
| 		} |  | ||||||
| 		if p.Method != d.Method { |  | ||||||
| 			t.Fatalf("Expected method %s got %s", d.Method, p.Method) |  | ||||||
| 		} |  | ||||||
| 		for k, v := range d.Header { |  | ||||||
| 			if val, ok := p.Header[k]; !ok { |  | ||||||
| 				t.Fatalf("Expected header %s", k) |  | ||||||
| 			} else if val.Values[0] != v[0] { |  | ||||||
| 				t.Fatalf("Expected val %s, got %s", val.Values[0], v[0]) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,141 +0,0 @@ | |||||||
| // Package event provides a handler which publishes an event |  | ||||||
| package event |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"encoding/json" |  | ||||||
| 	"fmt" |  | ||||||
| 	"net/http" |  | ||||||
| 	"path" |  | ||||||
| 	"regexp" |  | ||||||
| 	"strings" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/google/uuid" |  | ||||||
| 	"github.com/oxtoacart/bpool" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/handler" |  | ||||||
| 	proto "github.com/unistack-org/micro/v3/api/proto" |  | ||||||
| 	"github.com/unistack-org/micro/v3/util/ctx" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	bufferPool = bpool.NewSizedBufferPool(1024, 8) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type event struct { |  | ||||||
| 	opts handler.Options |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	Handler   = "event" |  | ||||||
| 	versionRe = regexp.MustCompilePOSIX("^v[0-9]+$") |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func eventName(parts []string) string { |  | ||||||
| 	return strings.Join(parts, ".") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func evRoute(ns, p string) (string, string) { |  | ||||||
| 	p = path.Clean(p) |  | ||||||
| 	p = strings.TrimPrefix(p, "/") |  | ||||||
|  |  | ||||||
| 	if len(p) == 0 { |  | ||||||
| 		return ns, "event" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	parts := strings.Split(p, "/") |  | ||||||
|  |  | ||||||
| 	// no path |  | ||||||
| 	if len(parts) == 0 { |  | ||||||
| 		// topic: namespace |  | ||||||
| 		// action: event |  | ||||||
| 		return strings.Trim(ns, "."), "event" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Treat /v[0-9]+ as versioning |  | ||||||
| 	// /v1/foo/bar => topic: v1.foo action: bar |  | ||||||
| 	if len(parts) >= 2 && versionRe.Match([]byte(parts[0])) { |  | ||||||
| 		topic := ns + "." + strings.Join(parts[:2], ".") |  | ||||||
| 		action := eventName(parts[1:]) |  | ||||||
| 		return topic, action |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// /foo => topic: ns.foo action: foo |  | ||||||
| 	// /foo/bar => topic: ns.foo action: bar |  | ||||||
| 	topic := ns + "." + strings.Join(parts[:1], ".") |  | ||||||
| 	action := eventName(parts[1:]) |  | ||||||
|  |  | ||||||
| 	return topic, action |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (e *event) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	bsize := handler.DefaultMaxRecvSize |  | ||||||
| 	if e.opts.MaxRecvSize > 0 { |  | ||||||
| 		bsize = e.opts.MaxRecvSize |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	r.Body = http.MaxBytesReader(w, r.Body, bsize) |  | ||||||
|  |  | ||||||
| 	// request to topic:event |  | ||||||
| 	// create event |  | ||||||
| 	// publish to topic |  | ||||||
|  |  | ||||||
| 	topic, action := evRoute(e.opts.Namespace, r.URL.Path) |  | ||||||
|  |  | ||||||
| 	// create event |  | ||||||
| 	ev := &proto.Event{ |  | ||||||
| 		Name: action, |  | ||||||
| 		// TODO: dedupe event |  | ||||||
| 		Id:        fmt.Sprintf("%s-%s-%s", topic, action, uuid.New().String()), |  | ||||||
| 		Header:    make(map[string]*proto.Pair), |  | ||||||
| 		Timestamp: time.Now().Unix(), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// set headers |  | ||||||
| 	for key, vals := range r.Header { |  | ||||||
| 		header, ok := ev.Header[key] |  | ||||||
| 		if !ok { |  | ||||||
| 			header = &proto.Pair{ |  | ||||||
| 				Key: key, |  | ||||||
| 			} |  | ||||||
| 			ev.Header[key] = header |  | ||||||
| 		} |  | ||||||
| 		header.Values = vals |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// set body |  | ||||||
| 	if r.Method == "GET" { |  | ||||||
| 		bytes, _ := json.Marshal(r.URL.Query()) |  | ||||||
| 		ev.Data = string(bytes) |  | ||||||
| 	} else { |  | ||||||
| 		// Read body |  | ||||||
| 		buf := bufferPool.Get() |  | ||||||
| 		defer bufferPool.Put(buf) |  | ||||||
| 		if _, err := buf.ReadFrom(r.Body); err != nil { |  | ||||||
| 			http.Error(w, err.Error(), 500) |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		ev.Data = buf.String() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// get client |  | ||||||
| 	c := e.opts.Client |  | ||||||
|  |  | ||||||
| 	// create publication |  | ||||||
| 	p := c.NewMessage(topic, ev) |  | ||||||
|  |  | ||||||
| 	// publish event |  | ||||||
| 	if err := c.Publish(ctx.FromRequest(r), p); err != nil { |  | ||||||
| 		http.Error(w, err.Error(), 500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (e *event) String() string { |  | ||||||
| 	return "event" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewHandler(opts ...handler.Option) handler.Handler { |  | ||||||
| 	return &event{ |  | ||||||
| 		opts: handler.NewOptions(opts...), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,105 +0,0 @@ | |||||||
| // Package http is a http reverse proxy handler |  | ||||||
| package http |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"math/rand" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/httputil" |  | ||||||
| 	"net/url" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/api" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/handler" |  | ||||||
| 	"github.com/unistack-org/micro/v3/register" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	Handler = "http" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type httpHandler struct { |  | ||||||
| 	options handler.Options |  | ||||||
|  |  | ||||||
| 	// set with different initializer |  | ||||||
| 	s *api.Service |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	service, err := h.getService(r) |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.WriteHeader(500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(service) == 0 { |  | ||||||
| 		w.WriteHeader(404) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	rp, err := url.Parse(service) |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.WriteHeader(500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // getService returns the service for this request from the selector |  | ||||||
| func (h *httpHandler) getService(r *http.Request) (string, error) { |  | ||||||
| 	var service *api.Service |  | ||||||
|  |  | ||||||
| 	if h.s != nil { |  | ||||||
| 		// we were given the service |  | ||||||
| 		service = h.s |  | ||||||
| 	} else if h.options.Router != nil { |  | ||||||
| 		// try get service from router |  | ||||||
| 		s, err := h.options.Router.Route(r) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", err |  | ||||||
| 		} |  | ||||||
| 		service = s |  | ||||||
| 	} else { |  | ||||||
| 		// we have no way of routing the request |  | ||||||
| 		return "", errors.New("no route found") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(service.Services) == 0 { |  | ||||||
| 		return "", errors.New("no route found") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// get the nodes for this service |  | ||||||
| 	nodes := make([]*register.Node, 0, len(service.Services)) |  | ||||||
| 	for _, srv := range service.Services { |  | ||||||
| 		nodes = append(nodes, srv.Nodes...) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// select a random node |  | ||||||
| 	node := nodes[rand.Int()%len(nodes)] |  | ||||||
|  |  | ||||||
| 	return fmt.Sprintf("http://%s", node.Address), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (h *httpHandler) String() string { |  | ||||||
| 	return "http" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewHandler returns a http proxy handler |  | ||||||
| func NewHandler(opts ...handler.Option) handler.Handler { |  | ||||||
| 	options := handler.NewOptions(opts...) |  | ||||||
|  |  | ||||||
| 	return &httpHandler{ |  | ||||||
| 		options: options, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // WithService creates a handler with a service |  | ||||||
| func WithService(s *api.Service, opts ...handler.Option) handler.Handler { |  | ||||||
| 	options := handler.NewOptions(opts...) |  | ||||||
|  |  | ||||||
| 	return &httpHandler{ |  | ||||||
| 		options: options, |  | ||||||
| 		s:       s, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,129 +0,0 @@ | |||||||
| // +build ignore |  | ||||||
|  |  | ||||||
| package http |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/httptest" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/handler" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/resolver" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/resolver/vpath" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/router" |  | ||||||
| 	regRouter "github.com/unistack-org/micro/v3/api/router/register" |  | ||||||
| 	"github.com/unistack-org/micro/v3/register" |  | ||||||
| 	"github.com/unistack-org/micro/v3/register/memory" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func testHttp(t *testing.T, path, service, ns string) { |  | ||||||
| 	r := memory.NewRegister() |  | ||||||
|  |  | ||||||
| 	l, err := net.Listen("tcp", "127.0.0.1:0") |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
| 	defer l.Close() |  | ||||||
|  |  | ||||||
| 	s := ®ister.Service{ |  | ||||||
| 		Name: service, |  | ||||||
| 		Nodes: []*register.Node{ |  | ||||||
| 			{ |  | ||||||
| 				Id:      service + "-1", |  | ||||||
| 				Address: l.Addr().String(), |  | ||||||
| 			}, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	r.Register(s) |  | ||||||
| 	defer r.Deregister(s) |  | ||||||
|  |  | ||||||
| 	// setup the test handler |  | ||||||
| 	m := http.NewServeMux() |  | ||||||
| 	m.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 		w.Write([]byte(`you got served`)) |  | ||||||
| 	}) |  | ||||||
|  |  | ||||||
| 	// start http test serve |  | ||||||
| 	go http.Serve(l, m) |  | ||||||
|  |  | ||||||
| 	// create new request and writer |  | ||||||
| 	w := httptest.NewRecorder() |  | ||||||
| 	req, err := http.NewRequest("POST", path, nil) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatal(err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// initialise the handler |  | ||||||
| 	rt := regRouter.NewRouter( |  | ||||||
| 		router.WithHandler("http"), |  | ||||||
| 		router.WithRegister(r), |  | ||||||
| 		router.WithResolver(vpath.NewResolver( |  | ||||||
| 			resolver.WithServicePrefix(ns), |  | ||||||
| 		)), |  | ||||||
| 	) |  | ||||||
|  |  | ||||||
| 	p := NewHandler(handler.WithRouter(rt)) |  | ||||||
|  |  | ||||||
| 	// execute the handler |  | ||||||
| 	p.ServeHTTP(w, req) |  | ||||||
|  |  | ||||||
| 	if w.Code != 200 { |  | ||||||
| 		t.Fatalf("Expected 200 response got %d %s", w.Code, w.Body.String()) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if w.Body.String() != "you got served" { |  | ||||||
| 		t.Fatalf("Expected body: you got served. Got: %s", w.Body.String()) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestHttpHandler(t *testing.T) { |  | ||||||
| 	testData := []struct { |  | ||||||
| 		path      string |  | ||||||
| 		service   string |  | ||||||
| 		namespace string |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			"/test/foo", |  | ||||||
| 			"go.micro.api.test", |  | ||||||
| 			"go.micro.api", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"/test/foo/baz", |  | ||||||
| 			"go.micro.api.test", |  | ||||||
| 			"go.micro.api", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"/v1/foo", |  | ||||||
| 			"go.micro.api.v1.foo", |  | ||||||
| 			"go.micro.api", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"/v1/foo/bar", |  | ||||||
| 			"go.micro.api.v1.foo", |  | ||||||
| 			"go.micro.api", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"/v2/baz", |  | ||||||
| 			"go.micro.api.v2.baz", |  | ||||||
| 			"go.micro.api", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"/v2/baz/bar", |  | ||||||
| 			"go.micro.api.v2.baz", |  | ||||||
| 			"go.micro.api", |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"/v2/baz/bar", |  | ||||||
| 			"v2.baz", |  | ||||||
| 			"", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	for _, d := range testData { |  | ||||||
| 		t.Run(d.service, func(t *testing.T) { |  | ||||||
| 			testHttp(t, d.path, d.service, d.namespace) |  | ||||||
| 		}) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -3,6 +3,7 @@ package handler | |||||||
| import ( | import ( | ||||||
| 	"github.com/unistack-org/micro/v3/api/router" | 	"github.com/unistack-org/micro/v3/api/router" | ||||||
| 	"github.com/unistack-org/micro/v3/client" | 	"github.com/unistack-org/micro/v3/client" | ||||||
|  | 	"github.com/unistack-org/micro/v3/logger" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| var ( | var ( | ||||||
| @@ -14,13 +15,19 @@ type Options struct { | |||||||
| 	Namespace   string | 	Namespace   string | ||||||
| 	Router      router.Router | 	Router      router.Router | ||||||
| 	Client      client.Client | 	Client      client.Client | ||||||
|  | 	Logger      logger.Logger | ||||||
| } | } | ||||||
|  |  | ||||||
| type Option func(o *Options) | type Option func(o *Options) | ||||||
|  |  | ||||||
| // NewOptions fills in the blanks | // NewOptions fills in the blanks | ||||||
| func NewOptions(opts ...Option) Options { | func NewOptions(opts ...Option) Options { | ||||||
| 	var options Options | 	options := Options{ | ||||||
|  | 		Client:      client.DefaultClient, | ||||||
|  | 		Router:      router.DefaultRouter, | ||||||
|  | 		Logger:      logger.DefaultLogger, | ||||||
|  | 		MaxRecvSize: DefaultMaxRecvSize, | ||||||
|  | 	} | ||||||
| 	for _, o := range opts { | 	for _, o := range opts { | ||||||
| 		o(&options) | 		o(&options) | ||||||
| 	} | 	} | ||||||
| @@ -30,10 +37,6 @@ func NewOptions(opts ...Option) Options { | |||||||
| 		WithNamespace("go.micro.api")(&options) | 		WithNamespace("go.micro.api")(&options) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if options.MaxRecvSize == 0 { |  | ||||||
| 		options.MaxRecvSize = DefaultMaxRecvSize |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return options | 	return options | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,182 +0,0 @@ | |||||||
| // Package web contains the web handler including websocket support |  | ||||||
| package web |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"errors" |  | ||||||
| 	"fmt" |  | ||||||
| 	"io" |  | ||||||
| 	"math/rand" |  | ||||||
| 	"net" |  | ||||||
| 	"net/http" |  | ||||||
| 	"net/http/httputil" |  | ||||||
| 	"net/url" |  | ||||||
| 	"strings" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/api" |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/handler" |  | ||||||
| 	"github.com/unistack-org/micro/v3/register" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	Handler = "web" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type webHandler struct { |  | ||||||
| 	opts handler.Options |  | ||||||
| 	s    *api.Service |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (wh *webHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	service, err := wh.getService(r) |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.WriteHeader(500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(service) == 0 { |  | ||||||
| 		w.WriteHeader(404) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	rp, err := url.Parse(service) |  | ||||||
| 	if err != nil { |  | ||||||
| 		w.WriteHeader(500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if isWebSocket(r) { |  | ||||||
| 		wh.serveWebSocket(rp.Host, w, r) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	httputil.NewSingleHostReverseProxy(rp).ServeHTTP(w, r) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // getService returns the service for this request from the selector |  | ||||||
| func (wh *webHandler) getService(r *http.Request) (string, error) { |  | ||||||
| 	var service *api.Service |  | ||||||
|  |  | ||||||
| 	if wh.s != nil { |  | ||||||
| 		// we were given the service |  | ||||||
| 		service = wh.s |  | ||||||
| 	} else if wh.opts.Router != nil { |  | ||||||
| 		// try get service from router |  | ||||||
| 		s, err := wh.opts.Router.Route(r) |  | ||||||
| 		if err != nil { |  | ||||||
| 			return "", err |  | ||||||
| 		} |  | ||||||
| 		service = s |  | ||||||
| 	} else { |  | ||||||
| 		// we have no way of routing the request |  | ||||||
| 		return "", errors.New("no route found") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// get the nodes |  | ||||||
| 	nodes := make([]*register.Node, 0, len(service.Services)) |  | ||||||
| 	for _, srv := range service.Services { |  | ||||||
| 		nodes = append(nodes, srv.Nodes...) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(nodes) == 0 { |  | ||||||
| 		return "", errors.New("no route found") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// select a random node |  | ||||||
| 	node := nodes[rand.Int()%len(nodes)] |  | ||||||
|  |  | ||||||
| 	return fmt.Sprintf("http://%s", node.Address), nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // serveWebSocket used to serve a web socket proxied connection |  | ||||||
| func (wh *webHandler) serveWebSocket(host string, w http.ResponseWriter, r *http.Request) { |  | ||||||
| 	req := new(http.Request) |  | ||||||
| 	*req = *r |  | ||||||
|  |  | ||||||
| 	if len(host) == 0 { |  | ||||||
| 		http.Error(w, "invalid host", 500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// set x-forward-for |  | ||||||
| 	if clientIP, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { |  | ||||||
| 		if ips, ok := req.Header["X-Forwarded-For"]; ok { |  | ||||||
| 			clientIP = strings.Join(ips, ", ") + ", " + clientIP |  | ||||||
| 		} |  | ||||||
| 		req.Header.Set("X-Forwarded-For", clientIP) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// connect to the backend host |  | ||||||
| 	conn, err := net.Dial("tcp", host) |  | ||||||
| 	if err != nil { |  | ||||||
| 		http.Error(w, err.Error(), 500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// hijack the connection |  | ||||||
| 	hj, ok := w.(http.Hijacker) |  | ||||||
| 	if !ok { |  | ||||||
| 		http.Error(w, "failed to connect", 500) |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	nc, _, err := hj.Hijack() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	defer nc.Close() |  | ||||||
| 	defer conn.Close() |  | ||||||
|  |  | ||||||
| 	if err = req.Write(conn); err != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	errCh := make(chan error, 2) |  | ||||||
|  |  | ||||||
| 	cp := func(dst io.Writer, src io.Reader) { |  | ||||||
| 		_, err := io.Copy(dst, src) |  | ||||||
| 		errCh <- err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	go cp(conn, nc) |  | ||||||
| 	go cp(nc, conn) |  | ||||||
|  |  | ||||||
| 	<-errCh |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func isWebSocket(r *http.Request) bool { |  | ||||||
| 	contains := func(key, val string) bool { |  | ||||||
| 		vv := strings.Split(r.Header.Get(key), ",") |  | ||||||
| 		for _, v := range vv { |  | ||||||
| 			if val == strings.ToLower(strings.TrimSpace(v)) { |  | ||||||
| 				return true |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return false |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if contains("Connection", "upgrade") && contains("Upgrade", "websocket") { |  | ||||||
| 		return true |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return false |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (wh *webHandler) String() string { |  | ||||||
| 	return "web" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewHandler(opts ...handler.Option) handler.Handler { |  | ||||||
| 	return &webHandler{ |  | ||||||
| 		opts: handler.NewOptions(opts...), |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func WithService(s *api.Service, opts ...handler.Option) handler.Handler { |  | ||||||
| 	options := handler.NewOptions(opts...) |  | ||||||
|  |  | ||||||
| 	return &webHandler{ |  | ||||||
| 		opts: options, |  | ||||||
| 		s:    s, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,28 +0,0 @@ | |||||||
| package proto |  | ||||||
|  |  | ||||||
| type Message struct { |  | ||||||
| 	data []byte |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *Message) ProtoMessage() {} |  | ||||||
|  |  | ||||||
| func (m *Message) Reset() { |  | ||||||
| 	*m = Message{} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *Message) String() string { |  | ||||||
| 	return string(m.data) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *Message) Marshal() ([]byte, error) { |  | ||||||
| 	return m.data, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *Message) Unmarshal(data []byte) error { |  | ||||||
| 	m.data = data |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func NewMessage(data []byte) *Message { |  | ||||||
| 	return &Message{data} |  | ||||||
| } |  | ||||||
| @@ -1,511 +0,0 @@ | |||||||
| // Code generated by protoc-gen-go. DO NOT EDIT. |  | ||||||
| // versions: |  | ||||||
| // 	protoc-gen-go v1.25.0 |  | ||||||
| // 	protoc        v3.6.1 |  | ||||||
| // source: api/proto/api.proto |  | ||||||
|  |  | ||||||
| package go_api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	proto "github.com/golang/protobuf/proto" |  | ||||||
| 	protoreflect "google.golang.org/protobuf/reflect/protoreflect" |  | ||||||
| 	protoimpl "google.golang.org/protobuf/runtime/protoimpl" |  | ||||||
| 	reflect "reflect" |  | ||||||
| 	sync "sync" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	// Verify that this generated code is sufficiently up-to-date. |  | ||||||
| 	_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) |  | ||||||
| 	// Verify that runtime/protoimpl is sufficiently up-to-date. |  | ||||||
| 	_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // This is a compile-time assertion that a sufficiently up-to-date version |  | ||||||
| // of the legacy proto package is being used. |  | ||||||
| const _ = proto.ProtoPackageIsVersion4 |  | ||||||
|  |  | ||||||
| type Pair struct { |  | ||||||
| 	state         protoimpl.MessageState |  | ||||||
| 	sizeCache     protoimpl.SizeCache |  | ||||||
| 	unknownFields protoimpl.UnknownFields |  | ||||||
|  |  | ||||||
| 	Key    string   `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` |  | ||||||
| 	Values []string `protobuf:"bytes,2,rep,name=values,proto3" json:"values,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Pair) Reset() { |  | ||||||
| 	*x = Pair{} |  | ||||||
| 	if protoimpl.UnsafeEnabled { |  | ||||||
| 		mi := &file_api_proto_api_proto_msgTypes[0] |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		ms.StoreMessageInfo(mi) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Pair) String() string { |  | ||||||
| 	return protoimpl.X.MessageStringOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (*Pair) ProtoMessage() {} |  | ||||||
|  |  | ||||||
| func (x *Pair) ProtoReflect() protoreflect.Message { |  | ||||||
| 	mi := &file_api_proto_api_proto_msgTypes[0] |  | ||||||
| 	if protoimpl.UnsafeEnabled && x != nil { |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		if ms.LoadMessageInfo() == nil { |  | ||||||
| 			ms.StoreMessageInfo(mi) |  | ||||||
| 		} |  | ||||||
| 		return ms |  | ||||||
| 	} |  | ||||||
| 	return mi.MessageOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Deprecated: Use Pair.ProtoReflect.Descriptor instead. |  | ||||||
| func (*Pair) Descriptor() ([]byte, []int) { |  | ||||||
| 	return file_api_proto_api_proto_rawDescGZIP(), []int{0} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Pair) GetKey() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Key |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Pair) GetValues() []string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Values |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A HTTP request as RPC |  | ||||||
| // Forward by the api handler |  | ||||||
| type Request struct { |  | ||||||
| 	state         protoimpl.MessageState |  | ||||||
| 	sizeCache     protoimpl.SizeCache |  | ||||||
| 	unknownFields protoimpl.UnknownFields |  | ||||||
|  |  | ||||||
| 	Method string           `protobuf:"bytes,1,opt,name=method,proto3" json:"method,omitempty"` |  | ||||||
| 	Path   string           `protobuf:"bytes,2,opt,name=path,proto3" json:"path,omitempty"` |  | ||||||
| 	Header map[string]*Pair `protobuf:"bytes,3,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` |  | ||||||
| 	Get    map[string]*Pair `protobuf:"bytes,4,rep,name=get,proto3" json:"get,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` |  | ||||||
| 	Post   map[string]*Pair `protobuf:"bytes,5,rep,name=post,proto3" json:"post,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` |  | ||||||
| 	Body   string           `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` // raw request body; if not application/x-www-form-urlencoded |  | ||||||
| 	Url    string           `protobuf:"bytes,7,opt,name=url,proto3" json:"url,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) Reset() { |  | ||||||
| 	*x = Request{} |  | ||||||
| 	if protoimpl.UnsafeEnabled { |  | ||||||
| 		mi := &file_api_proto_api_proto_msgTypes[1] |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		ms.StoreMessageInfo(mi) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) String() string { |  | ||||||
| 	return protoimpl.X.MessageStringOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (*Request) ProtoMessage() {} |  | ||||||
|  |  | ||||||
| func (x *Request) ProtoReflect() protoreflect.Message { |  | ||||||
| 	mi := &file_api_proto_api_proto_msgTypes[1] |  | ||||||
| 	if protoimpl.UnsafeEnabled && x != nil { |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		if ms.LoadMessageInfo() == nil { |  | ||||||
| 			ms.StoreMessageInfo(mi) |  | ||||||
| 		} |  | ||||||
| 		return ms |  | ||||||
| 	} |  | ||||||
| 	return mi.MessageOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Deprecated: Use Request.ProtoReflect.Descriptor instead. |  | ||||||
| func (*Request) Descriptor() ([]byte, []int) { |  | ||||||
| 	return file_api_proto_api_proto_rawDescGZIP(), []int{1} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetMethod() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Method |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetPath() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Path |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetHeader() map[string]*Pair { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Header |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetGet() map[string]*Pair { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Get |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetPost() map[string]*Pair { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Post |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetBody() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Body |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Request) GetUrl() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Url |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A HTTP response as RPC |  | ||||||
| // Expected response for the api handler |  | ||||||
| type Response struct { |  | ||||||
| 	state         protoimpl.MessageState |  | ||||||
| 	sizeCache     protoimpl.SizeCache |  | ||||||
| 	unknownFields protoimpl.UnknownFields |  | ||||||
|  |  | ||||||
| 	StatusCode int32            `protobuf:"varint,1,opt,name=statusCode,proto3" json:"statusCode,omitempty"` |  | ||||||
| 	Header     map[string]*Pair `protobuf:"bytes,2,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` |  | ||||||
| 	Body       string           `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Response) Reset() { |  | ||||||
| 	*x = Response{} |  | ||||||
| 	if protoimpl.UnsafeEnabled { |  | ||||||
| 		mi := &file_api_proto_api_proto_msgTypes[2] |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		ms.StoreMessageInfo(mi) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Response) String() string { |  | ||||||
| 	return protoimpl.X.MessageStringOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (*Response) ProtoMessage() {} |  | ||||||
|  |  | ||||||
| func (x *Response) ProtoReflect() protoreflect.Message { |  | ||||||
| 	mi := &file_api_proto_api_proto_msgTypes[2] |  | ||||||
| 	if protoimpl.UnsafeEnabled && x != nil { |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		if ms.LoadMessageInfo() == nil { |  | ||||||
| 			ms.StoreMessageInfo(mi) |  | ||||||
| 		} |  | ||||||
| 		return ms |  | ||||||
| 	} |  | ||||||
| 	return mi.MessageOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Deprecated: Use Response.ProtoReflect.Descriptor instead. |  | ||||||
| func (*Response) Descriptor() ([]byte, []int) { |  | ||||||
| 	return file_api_proto_api_proto_rawDescGZIP(), []int{2} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Response) GetStatusCode() int32 { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.StatusCode |  | ||||||
| 	} |  | ||||||
| 	return 0 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Response) GetHeader() map[string]*Pair { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Header |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Response) GetBody() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Body |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A HTTP event as RPC |  | ||||||
| // Forwarded by the event handler |  | ||||||
| type Event struct { |  | ||||||
| 	state         protoimpl.MessageState |  | ||||||
| 	sizeCache     protoimpl.SizeCache |  | ||||||
| 	unknownFields protoimpl.UnknownFields |  | ||||||
|  |  | ||||||
| 	// e.g login |  | ||||||
| 	Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` |  | ||||||
| 	// uuid |  | ||||||
| 	Id string `protobuf:"bytes,2,opt,name=id,proto3" json:"id,omitempty"` |  | ||||||
| 	// unix timestamp of event |  | ||||||
| 	Timestamp int64 `protobuf:"varint,3,opt,name=timestamp,proto3" json:"timestamp,omitempty"` |  | ||||||
| 	// event headers |  | ||||||
| 	Header map[string]*Pair `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` |  | ||||||
| 	// the event data |  | ||||||
| 	Data string `protobuf:"bytes,5,opt,name=data,proto3" json:"data,omitempty"` |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) Reset() { |  | ||||||
| 	*x = Event{} |  | ||||||
| 	if protoimpl.UnsafeEnabled { |  | ||||||
| 		mi := &file_api_proto_api_proto_msgTypes[3] |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		ms.StoreMessageInfo(mi) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) String() string { |  | ||||||
| 	return protoimpl.X.MessageStringOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (*Event) ProtoMessage() {} |  | ||||||
|  |  | ||||||
| func (x *Event) ProtoReflect() protoreflect.Message { |  | ||||||
| 	mi := &file_api_proto_api_proto_msgTypes[3] |  | ||||||
| 	if protoimpl.UnsafeEnabled && x != nil { |  | ||||||
| 		ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) |  | ||||||
| 		if ms.LoadMessageInfo() == nil { |  | ||||||
| 			ms.StoreMessageInfo(mi) |  | ||||||
| 		} |  | ||||||
| 		return ms |  | ||||||
| 	} |  | ||||||
| 	return mi.MessageOf(x) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Deprecated: Use Event.ProtoReflect.Descriptor instead. |  | ||||||
| func (*Event) Descriptor() ([]byte, []int) { |  | ||||||
| 	return file_api_proto_api_proto_rawDescGZIP(), []int{3} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) GetName() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Name |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) GetId() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Id |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) GetTimestamp() int64 { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Timestamp |  | ||||||
| 	} |  | ||||||
| 	return 0 |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) GetHeader() map[string]*Pair { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Header |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (x *Event) GetData() string { |  | ||||||
| 	if x != nil { |  | ||||||
| 		return x.Data |  | ||||||
| 	} |  | ||||||
| 	return "" |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var File_api_proto_api_proto protoreflect.FileDescriptor |  | ||||||
|  |  | ||||||
| var file_api_proto_api_proto_rawDesc = []byte{ |  | ||||||
| 	0x0a, 0x13, 0x61, 0x70, 0x69, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2e, |  | ||||||
| 	0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x22, 0x30, 0x0a, |  | ||||||
| 	0x04, 0x50, 0x61, 0x69, 0x72, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, |  | ||||||
| 	0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, |  | ||||||
| 	0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, |  | ||||||
| 	0xc1, 0x03, 0x0a, 0x07, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, |  | ||||||
| 	0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, 0x74, |  | ||||||
| 	0x68, 0x6f, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, |  | ||||||
| 	0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x33, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, |  | ||||||
| 	0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, |  | ||||||
| 	0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, |  | ||||||
| 	0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x2a, 0x0a, 0x03, |  | ||||||
| 	0x67, 0x65, 0x74, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x67, 0x6f, 0x2e, 0x61, |  | ||||||
| 	0x70, 0x69, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x6e, |  | ||||||
| 	0x74, 0x72, 0x79, 0x52, 0x03, 0x67, 0x65, 0x74, 0x12, 0x2d, 0x0a, 0x04, 0x70, 0x6f, 0x73, 0x74, |  | ||||||
| 	0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, |  | ||||||
| 	0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x50, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, |  | ||||||
| 	0x79, 0x52, 0x04, 0x70, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, |  | ||||||
| 	0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, |  | ||||||
| 	0x72, 0x6c, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x1a, 0x47, 0x0a, |  | ||||||
| 	0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, |  | ||||||
| 	0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, |  | ||||||
| 	0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, |  | ||||||
| 	0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, |  | ||||||
| 	0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x44, 0x0a, 0x08, 0x47, 0x65, 0x74, 0x45, 0x6e, 0x74, |  | ||||||
| 	0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, |  | ||||||
| 	0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, |  | ||||||
| 	0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, |  | ||||||
| 	0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x45, 0x0a, 0x09, |  | ||||||
| 	0x50, 0x6f, 0x73, 0x74, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, |  | ||||||
| 	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76, |  | ||||||
| 	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e, |  | ||||||
| 	0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, |  | ||||||
| 	0x02, 0x38, 0x01, 0x22, 0xbd, 0x01, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, |  | ||||||
| 	0x12, 0x1e, 0x0a, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, 0x18, 0x01, |  | ||||||
| 	0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, 0x64, 0x65, |  | ||||||
| 	0x12, 0x34, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, |  | ||||||
| 	0x32, 0x1c, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, |  | ||||||
| 	0x73, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, |  | ||||||
| 	0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, |  | ||||||
| 	0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, 0x1a, 0x47, 0x0a, 0x0b, 0x48, 0x65, |  | ||||||
| 	0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, |  | ||||||
| 	0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76, |  | ||||||
| 	0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e, |  | ||||||
| 	0x61, 0x70, 0x69, 0x2e, 0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, |  | ||||||
| 	0x02, 0x38, 0x01, 0x22, 0xd9, 0x01, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x12, 0x0a, |  | ||||||
| 	0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, |  | ||||||
| 	0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, |  | ||||||
| 	0x64, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x03, |  | ||||||
| 	0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, |  | ||||||
| 	0x31, 0x0a, 0x06, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, |  | ||||||
| 	0x19, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x48, |  | ||||||
| 	0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x68, 0x65, 0x61, 0x64, |  | ||||||
| 	0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x64, 0x61, 0x74, 0x61, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, |  | ||||||
| 	0x52, 0x04, 0x64, 0x61, 0x74, 0x61, 0x1a, 0x47, 0x0a, 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, |  | ||||||
| 	0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, |  | ||||||
| 	0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x22, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, |  | ||||||
| 	0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x67, 0x6f, 0x2e, 0x61, 0x70, 0x69, 0x2e, |  | ||||||
| 	0x50, 0x61, 0x69, 0x72, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x62, |  | ||||||
| 	0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	file_api_proto_api_proto_rawDescOnce sync.Once |  | ||||||
| 	file_api_proto_api_proto_rawDescData = file_api_proto_api_proto_rawDesc |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func file_api_proto_api_proto_rawDescGZIP() []byte { |  | ||||||
| 	file_api_proto_api_proto_rawDescOnce.Do(func() { |  | ||||||
| 		file_api_proto_api_proto_rawDescData = protoimpl.X.CompressGZIP(file_api_proto_api_proto_rawDescData) |  | ||||||
| 	}) |  | ||||||
| 	return file_api_proto_api_proto_rawDescData |  | ||||||
| } |  | ||||||
|  |  | ||||||
| var file_api_proto_api_proto_msgTypes = make([]protoimpl.MessageInfo, 9) |  | ||||||
| var file_api_proto_api_proto_goTypes = []interface{}{ |  | ||||||
| 	(*Pair)(nil),     // 0: go.api.Pair |  | ||||||
| 	(*Request)(nil),  // 1: go.api.Request |  | ||||||
| 	(*Response)(nil), // 2: go.api.Response |  | ||||||
| 	(*Event)(nil),    // 3: go.api.Event |  | ||||||
| 	nil,              // 4: go.api.Request.HeaderEntry |  | ||||||
| 	nil,              // 5: go.api.Request.GetEntry |  | ||||||
| 	nil,              // 6: go.api.Request.PostEntry |  | ||||||
| 	nil,              // 7: go.api.Response.HeaderEntry |  | ||||||
| 	nil,              // 8: go.api.Event.HeaderEntry |  | ||||||
| } |  | ||||||
| var file_api_proto_api_proto_depIdxs = []int32{ |  | ||||||
| 	4,  // 0: go.api.Request.header:type_name -> go.api.Request.HeaderEntry |  | ||||||
| 	5,  // 1: go.api.Request.get:type_name -> go.api.Request.GetEntry |  | ||||||
| 	6,  // 2: go.api.Request.post:type_name -> go.api.Request.PostEntry |  | ||||||
| 	7,  // 3: go.api.Response.header:type_name -> go.api.Response.HeaderEntry |  | ||||||
| 	8,  // 4: go.api.Event.header:type_name -> go.api.Event.HeaderEntry |  | ||||||
| 	0,  // 5: go.api.Request.HeaderEntry.value:type_name -> go.api.Pair |  | ||||||
| 	0,  // 6: go.api.Request.GetEntry.value:type_name -> go.api.Pair |  | ||||||
| 	0,  // 7: go.api.Request.PostEntry.value:type_name -> go.api.Pair |  | ||||||
| 	0,  // 8: go.api.Response.HeaderEntry.value:type_name -> go.api.Pair |  | ||||||
| 	0,  // 9: go.api.Event.HeaderEntry.value:type_name -> go.api.Pair |  | ||||||
| 	10, // [10:10] is the sub-list for method output_type |  | ||||||
| 	10, // [10:10] is the sub-list for method input_type |  | ||||||
| 	10, // [10:10] is the sub-list for extension type_name |  | ||||||
| 	10, // [10:10] is the sub-list for extension extendee |  | ||||||
| 	0,  // [0:10] is the sub-list for field type_name |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func init() { file_api_proto_api_proto_init() } |  | ||||||
| func file_api_proto_api_proto_init() { |  | ||||||
| 	if File_api_proto_api_proto != nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	if !protoimpl.UnsafeEnabled { |  | ||||||
| 		file_api_proto_api_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*Pair); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		file_api_proto_api_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*Request); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		file_api_proto_api_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*Response); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		file_api_proto_api_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { |  | ||||||
| 			switch v := v.(*Event); i { |  | ||||||
| 			case 0: |  | ||||||
| 				return &v.state |  | ||||||
| 			case 1: |  | ||||||
| 				return &v.sizeCache |  | ||||||
| 			case 2: |  | ||||||
| 				return &v.unknownFields |  | ||||||
| 			default: |  | ||||||
| 				return nil |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	type x struct{} |  | ||||||
| 	out := protoimpl.TypeBuilder{ |  | ||||||
| 		File: protoimpl.DescBuilder{ |  | ||||||
| 			GoPackagePath: reflect.TypeOf(x{}).PkgPath(), |  | ||||||
| 			RawDescriptor: file_api_proto_api_proto_rawDesc, |  | ||||||
| 			NumEnums:      0, |  | ||||||
| 			NumMessages:   9, |  | ||||||
| 			NumExtensions: 0, |  | ||||||
| 			NumServices:   0, |  | ||||||
| 		}, |  | ||||||
| 		GoTypes:           file_api_proto_api_proto_goTypes, |  | ||||||
| 		DependencyIndexes: file_api_proto_api_proto_depIdxs, |  | ||||||
| 		MessageInfos:      file_api_proto_api_proto_msgTypes, |  | ||||||
| 	}.Build() |  | ||||||
| 	File_api_proto_api_proto = out.File |  | ||||||
| 	file_api_proto_api_proto_rawDesc = nil |  | ||||||
| 	file_api_proto_api_proto_goTypes = nil |  | ||||||
| 	file_api_proto_api_proto_depIdxs = nil |  | ||||||
| } |  | ||||||
| @@ -1,21 +0,0 @@ | |||||||
| // Code generated by protoc-gen-micro. DO NOT EDIT. |  | ||||||
| // source: api/proto/api.proto |  | ||||||
|  |  | ||||||
| package go_api |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	fmt "fmt" |  | ||||||
| 	proto "github.com/golang/protobuf/proto" |  | ||||||
| 	math "math" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Reference imports to suppress errors if they are not otherwise used. |  | ||||||
| var _ = proto.Marshal |  | ||||||
| var _ = fmt.Errorf |  | ||||||
| var _ = math.Inf |  | ||||||
|  |  | ||||||
| // This is a compile-time assertion to ensure that this generated file |  | ||||||
| // is compatible with the proto package it is being compiled against. |  | ||||||
| // A compilation error at this line likely means your copy of the |  | ||||||
| // proto package needs to be updated. |  | ||||||
| const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package |  | ||||||
| @@ -1,43 +0,0 @@ | |||||||
| syntax = "proto3"; |  | ||||||
|  |  | ||||||
| package go.api; |  | ||||||
|  |  | ||||||
| message Pair { |  | ||||||
| 	string key = 1; |  | ||||||
| 	repeated string values = 2; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A HTTP request as RPC |  | ||||||
| // Forward by the api handler |  | ||||||
| message Request { |  | ||||||
|         string method = 1; |  | ||||||
|         string path = 2; |  | ||||||
|         map<string, Pair> header = 3; |  | ||||||
|         map<string, Pair> get = 4; |  | ||||||
|         map<string, Pair> post = 5; |  | ||||||
|         string body = 6;  // raw request body; if not application/x-www-form-urlencoded |  | ||||||
| 	string url = 7; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A HTTP response as RPC |  | ||||||
| // Expected response for the api handler |  | ||||||
| message Response { |  | ||||||
|         int32 statusCode = 1; |  | ||||||
|         map<string, Pair> header = 2; |  | ||||||
|         string body = 3; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // A HTTP event as RPC |  | ||||||
| // Forwarded by the event handler |  | ||||||
| message Event { |  | ||||||
| 	// e.g login |  | ||||||
| 	string name = 1; |  | ||||||
| 	// uuid |  | ||||||
| 	string id = 2; |  | ||||||
| 	// unix timestamp of event |  | ||||||
| 	int64 timestamp = 3; |  | ||||||
| 	// event headers |  | ||||||
|         map<string, Pair> header = 4; |  | ||||||
| 	// the event data |  | ||||||
| 	string data = 5; |  | ||||||
| } |  | ||||||
| @@ -6,8 +6,6 @@ import ( | |||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/api/resolver/vpath" | 	"github.com/unistack-org/micro/v3/api/resolver/vpath" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestResolve(t *testing.T) { | func TestResolve(t *testing.T) { | ||||||
| @@ -62,9 +60,13 @@ func TestResolve(t *testing.T) { | |||||||
| 		t.Run(tc.Name, func(t *testing.T) { | 		t.Run(tc.Name, func(t *testing.T) { | ||||||
| 			r := NewResolver(vpath.NewResolver()) | 			r := NewResolver(vpath.NewResolver()) | ||||||
| 			result, err := r.Resolve(&http.Request{URL: &url.URL{Host: tc.Host, Path: "foo/bar"}}) | 			result, err := r.Resolve(&http.Request{URL: &url.URL{Host: tc.Host, Path: "foo/bar"}}) | ||||||
| 			assert.Nil(t, err, "Expecter err to be nil") | 			if err != nil { | ||||||
|  | 				t.Fatal(err) | ||||||
|  | 			} | ||||||
| 			if result != nil { | 			if result != nil { | ||||||
| 				assert.Equal(t, tc.Result, result.Domain, "Expected %v but got %v", tc.Result, result.Domain) | 				if tc.Result != result.Domain { | ||||||
|  | 					t.Fatalf("Expected %v but got %v", tc.Result, result.Domain) | ||||||
|  | 				} | ||||||
| 			} | 			} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -7,6 +7,10 @@ import ( | |||||||
| 	"github.com/unistack-org/micro/v3/api" | 	"github.com/unistack-org/micro/v3/api" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | var ( | ||||||
|  | 	DefaultRouter Router | ||||||
|  | ) | ||||||
|  |  | ||||||
| // Router is used to determine an endpoint for a request | // Router is used to determine an endpoint for a request | ||||||
| type Router interface { | type Router interface { | ||||||
| 	// Returns options | 	// Returns options | ||||||
| @@ -23,6 +27,6 @@ type Router interface { | |||||||
| 	Deregister(ep *api.Endpoint) error | 	Deregister(ep *api.Endpoint) error | ||||||
| 	// Route returns an api.Service route | 	// Route returns an api.Service route | ||||||
| 	Route(r *http.Request) (*api.Service, error) | 	Route(r *http.Request) (*api.Service, error) | ||||||
| 	// String represenation of router | 	// String representation of router | ||||||
| 	String() string | 	String() string | ||||||
| } | } | ||||||
|   | |||||||
| @@ -235,6 +235,7 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NewBroker return new memory broker | ||||||
| func NewBroker(opts ...Option) Broker { | func NewBroker(opts ...Option) Broker { | ||||||
| 	rand.Seed(time.Now().UnixNano()) | 	rand.Seed(time.Now().UnixNano()) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ import ( | |||||||
| var ( | var ( | ||||||
| 	// DefaultClient is the global default client | 	// DefaultClient is the global default client | ||||||
| 	DefaultClient      Client = NewClient() | 	DefaultClient      Client = NewClient() | ||||||
|  | 	DefaultContentType        = "application/json" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Client is the interface used to make requests to services. | // Client is the interface used to make requests to services. | ||||||
|   | |||||||
| @@ -20,10 +20,6 @@ var ( | |||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	defaultContentType = "application/json" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type noopClient struct { | type noopClient struct { | ||||||
| 	opts Options | 	opts Options | ||||||
| } | } | ||||||
|   | |||||||
| @@ -151,7 +151,7 @@ type RequestOptions struct { | |||||||
| func NewOptions(opts ...Option) Options { | func NewOptions(opts ...Option) Options { | ||||||
| 	options := Options{ | 	options := Options{ | ||||||
| 		Context:     context.Background(), | 		Context:     context.Background(), | ||||||
| 		ContentType: "application/json", | 		ContentType: DefaultContentType, | ||||||
| 		Codecs:      make(map[string]codec.Codec), | 		Codecs:      make(map[string]codec.Codec), | ||||||
| 		CallOptions: CallOptions{ | 		CallOptions: CallOptions{ | ||||||
| 			Backoff:        DefaultBackoff, | 			Backoff:        DefaultBackoff, | ||||||
|   | |||||||
| @@ -8,8 +8,8 @@ import ( | |||||||
| 	"github.com/unistack-org/micro/v3/metadata" | 	"github.com/unistack-org/micro/v3/metadata" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| const ( |  | ||||||
| // Message types | // Message types | ||||||
|  | const ( | ||||||
| 	Error MessageType = iota | 	Error MessageType = iota | ||||||
| 	Request | 	Request | ||||||
| 	Response | 	Response | ||||||
| @@ -26,6 +26,7 @@ var ( | |||||||
| var ( | var ( | ||||||
| 	// DefaultMaxMsgSize specifies how much data codec can handle | 	// DefaultMaxMsgSize specifies how much data codec can handle | ||||||
| 	DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb | 	DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb | ||||||
|  | 	// DefaultCodec is the global default codec | ||||||
| 	DefaultCodec Codec = NewCodec() | 	DefaultCodec Codec = NewCodec() | ||||||
| ) | ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -30,12 +30,8 @@ func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	switch v := b.(type) { | 	switch v := b.(type) { | ||||||
| 	case string: |  | ||||||
| 		v = string(buf) |  | ||||||
| 	case *string: | 	case *string: | ||||||
| 		*v = string(buf) | 		*v = string(buf) | ||||||
| 	case []byte: |  | ||||||
| 		v = buf |  | ||||||
| 	case *[]byte: | 	case *[]byte: | ||||||
| 		*v = buf | 		*v = buf | ||||||
| 	case *Frame: | 	case *Frame: | ||||||
| @@ -112,15 +108,9 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}) error { | |||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| 	switch ve := v.(type) { | 	switch ve := v.(type) { | ||||||
| 	case string: |  | ||||||
| 		ve = string(d) |  | ||||||
| 		return nil |  | ||||||
| 	case *string: | 	case *string: | ||||||
| 		*ve = string(d) | 		*ve = string(d) | ||||||
| 		return nil | 		return nil | ||||||
| 	case []byte: |  | ||||||
| 		ve = d |  | ||||||
| 		return nil |  | ||||||
| 	case *[]byte: | 	case *[]byte: | ||||||
| 		*ve = d | 		*ve = d | ||||||
| 		return nil | 		return nil | ||||||
|   | |||||||
							
								
								
									
										34
									
								
								codec/noop_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								codec/noop_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | package codec | ||||||
|  |  | ||||||
|  | import ( | ||||||
|  | 	"bytes" | ||||||
|  | 	"testing" | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | func TestNoopBytes(t *testing.T) { | ||||||
|  | 	req := []byte("test req") | ||||||
|  | 	rsp := make([]byte, len(req)) | ||||||
|  |  | ||||||
|  | 	nc := NewCodec() | ||||||
|  | 	if err := nc.Unmarshal(req, &rsp); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !bytes.Equal(req, rsp) { | ||||||
|  | 		t.Fatalf("req not eq rsp: %s != %s", req, rsp) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | func TestNoopString(t *testing.T) { | ||||||
|  | 	req := []byte("test req") | ||||||
|  | 	var rsp string | ||||||
|  |  | ||||||
|  | 	nc := NewCodec() | ||||||
|  | 	if err := nc.Unmarshal(req, &rsp); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	if !bytes.Equal(req, []byte(rsp)) { | ||||||
|  | 		t.Fatalf("req not eq rsp: %s != %s", req, rsp) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -45,6 +45,7 @@ func Meter(m meter.Meter) Option { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NewOptions returns new options | ||||||
| func NewOptions(opts ...Option) Options { | func NewOptions(opts ...Option) Options { | ||||||
| 	options := Options{ | 	options := Options{ | ||||||
| 		Logger:     logger.DefaultLogger, | 		Logger:     logger.DefaultLogger, | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ import ( | |||||||
|  |  | ||||||
| type configKey struct{} | type configKey struct{} | ||||||
|  |  | ||||||
|  | // FromContext returns store from context | ||||||
| func FromContext(ctx context.Context) (Config, bool) { | func FromContext(ctx context.Context) (Config, bool) { | ||||||
| 	if ctx == nil { | 	if ctx == nil { | ||||||
| 		return nil, false | 		return nil, false | ||||||
| @@ -14,6 +15,7 @@ func FromContext(ctx context.Context) (Config, bool) { | |||||||
| 	return c, ok | 	return c, ok | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // NewContext put store in context | ||||||
| func NewContext(ctx context.Context, c Config) context.Context { | func NewContext(ctx context.Context, c Config) context.Context { | ||||||
| 	if ctx == nil { | 	if ctx == nil { | ||||||
| 		ctx = context.Background() | 		ctx = context.Background() | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
| @@ -64,7 +64,7 @@ func newFunction(opts ...Option) Function { | |||||||
| 	// make context the last thing | 	// make context the last thing | ||||||
| 	fopts = append(fopts, Context(ctx)) | 	fopts = append(fopts, Context(ctx)) | ||||||
|  |  | ||||||
| 	service := &service{opts: NewOptions(opts...)} | 	service := &service{opts: NewOptions(fopts...)} | ||||||
|  |  | ||||||
| 	fn := &function{ | 	fn := &function{ | ||||||
| 		cancel:  cancel, | 		cancel:  cancel, | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								go.mod
									
									
									
									
									
								
							| @@ -6,18 +6,12 @@ require ( | |||||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||||
| 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | 	github.com/dgrijalva/jwt-go v3.2.0+incompatible | ||||||
| 	github.com/ef-ds/deque v1.0.4 | 	github.com/ef-ds/deque v1.0.4 | ||||||
| 	github.com/golang/protobuf v1.4.3 |  | ||||||
| 	github.com/google/uuid v1.2.0 | 	github.com/google/uuid v1.2.0 | ||||||
| 	github.com/imdario/mergo v0.3.11 | 	github.com/imdario/mergo v0.3.11 | ||||||
| 	github.com/kr/text v0.2.0 // indirect | 	github.com/kr/text v0.2.0 // indirect | ||||||
| 	github.com/miekg/dns v1.1.38 |  | ||||||
| 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect | 	github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect | ||||||
| 	github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c |  | ||||||
| 	github.com/patrickmn/go-cache v2.1.0+incompatible | 	github.com/patrickmn/go-cache v2.1.0+incompatible | ||||||
| 	github.com/stretchr/testify v1.7.0 | 	github.com/stretchr/testify v1.7.0 | ||||||
| 	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect |  | ||||||
| 	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 | 	golang.org/x/net v0.0.0-20210119194325-5f4716e94777 | ||||||
| 	golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect |  | ||||||
| 	google.golang.org/protobuf v1.25.0 |  | ||||||
| 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect | 	gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect | ||||||
| ) | ) | ||||||
|   | |||||||
							
								
								
									
										88
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										88
									
								
								go.sum
									
									
									
									
									
								
							| @@ -1,8 +1,3 @@ | |||||||
| cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= |  | ||||||
| github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= |  | ||||||
| github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= |  | ||||||
| github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= |  | ||||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= |  | ||||||
| github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= | ||||||
| github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||||||
| github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||||||
| @@ -11,26 +6,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumC | |||||||
| github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= | ||||||
| github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= | github.com/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= | ||||||
| github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= | github.com/ef-ds/deque v1.0.4/go.mod h1:gXDnTC3yqvBcHbq2lcExjtAcVrOnJCbMcZXmuj8Z4tg= | ||||||
| github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= |  | ||||||
| github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= |  | ||||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= |  | ||||||
| github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= |  | ||||||
| github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |  | ||||||
| github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= |  | ||||||
| github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= |  | ||||||
| github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= |  | ||||||
| github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= |  | ||||||
| github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= |  | ||||||
| github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= |  | ||||||
| github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= |  | ||||||
| github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |  | ||||||
| github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= |  | ||||||
| github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |  | ||||||
| github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= |  | ||||||
| github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= |  | ||||||
| github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= | github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs= | ||||||
| github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= | ||||||
| github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= | github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= | ||||||
| @@ -39,84 +14,23 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= | |||||||
| github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= | ||||||
| github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= | ||||||
| github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= | ||||||
| github.com/miekg/dns v1.1.38 h1:MtIY+fmHUVVgv1AXzmKMWcwdCYxTRPG1EDjpqF4RCEw= |  | ||||||
| github.com/miekg/dns v1.1.38/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= |  | ||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= | ||||||
| github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= | ||||||
| github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw= |  | ||||||
| github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= |  | ||||||
| github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= | github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= | ||||||
| github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= | github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= | ||||||
| github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||||||
| github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||||||
| github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= |  | ||||||
| github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||||||
| github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |  | ||||||
| github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= | ||||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||||
| golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= |  | ||||||
| golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= |  | ||||||
| golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad h1:DN0cp81fZ3njFcrLCytUHRSUkqBjfTo4Tx9RJTWs0EY= |  | ||||||
| golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= |  | ||||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= |  | ||||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= |  | ||||||
| golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= |  | ||||||
| golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= |  | ||||||
| golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= |  | ||||||
| golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |  | ||||||
| golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |  | ||||||
| golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= |  | ||||||
| golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |  | ||||||
| golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= |  | ||||||
| golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |  | ||||||
| golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= |  | ||||||
| golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= | golang.org/x/net v0.0.0-20210119194325-5f4716e94777 h1:003p0dJM77cxMSyCPFphvZf/Y5/NXf5fzg6ufd1/Oew= | ||||||
| golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= | ||||||
| golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= |  | ||||||
| golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= |  | ||||||
| golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= |  | ||||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |  | ||||||
| golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= |  | ||||||
| golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |  | ||||||
| golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |  | ||||||
| golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= |  | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68 h1:nxC68pudNYkKU6jWhgrqdreuFiOQWj1Fs7T3VrH4Pjw= | ||||||
| golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= | ||||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= |  | ||||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= |  | ||||||
| golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= | ||||||
| golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||||
| golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= |  | ||||||
| golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= |  | ||||||
| golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= |  | ||||||
| golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= |  | ||||||
| golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= |  | ||||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |  | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= |  | ||||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= |  | ||||||
| google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= |  | ||||||
| google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= |  | ||||||
| google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= |  | ||||||
| google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= |  | ||||||
| google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= |  | ||||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= |  | ||||||
| google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= |  | ||||||
| google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= |  | ||||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= |  | ||||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= |  | ||||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= |  | ||||||
| google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= |  | ||||||
| google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= |  | ||||||
| google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |  | ||||||
| google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |  | ||||||
| google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= |  | ||||||
| google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= |  | ||||||
| google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= |  | ||||||
| gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= | ||||||
| gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||||||
| @@ -124,5 +38,3 @@ gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= | |||||||
| gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= | ||||||
| gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||||||
| honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |  | ||||||
| honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= |  | ||||||
|   | |||||||
| @@ -2,19 +2,16 @@ package meter | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestNoopMeter(t *testing.T) { | func TestNoopMeter(t *testing.T) { | ||||||
| 	meter := NewMeter(Path("/noop")) | 	meter := NewMeter(Path("/noop")) | ||||||
| 	assert.NotNil(t, meter) | 	if "/noop" != meter.Options().Path { | ||||||
| 	assert.Equal(t, "/noop", meter.Options().Path) | 		t.Fatalf("invalid options parsing: %v", meter.Options()) | ||||||
| 	assert.Implements(t, new(Meter), meter) | 	} | ||||||
|  |  | ||||||
| 	cnt := meter.Counter("counter", Label("server", "noop")) | 	cnt := meter.Counter("counter", Label("server", "noop")) | ||||||
| 	cnt.Inc() | 	cnt.Inc() | ||||||
|  |  | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestLabels(t *testing.T) { | func TestLabels(t *testing.T) { | ||||||
|   | |||||||
| @@ -4,8 +4,9 @@ package dns | |||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"net" | 	"net" | ||||||
|  | 	"sync" | ||||||
|  | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| 	"github.com/unistack-org/micro/v3/resolver" | 	"github.com/unistack-org/micro/v3/resolver" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -13,9 +14,10 @@ import ( | |||||||
| type Resolver struct { | type Resolver struct { | ||||||
| 	// The resolver address to use | 	// The resolver address to use | ||||||
| 	Address    string | 	Address    string | ||||||
|  | 	goresolver *net.Resolver | ||||||
|  | 	sync.RWMutex | ||||||
| } | } | ||||||
|  |  | ||||||
| // Resolve assumes ID is a domain name e.g micro.mu |  | ||||||
| func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { | func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { | ||||||
| 	host, port, err := net.SplitHostPort(name) | 	host, port, err := net.SplitHostPort(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| @@ -28,56 +30,46 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { | |||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if len(r.Address) == 0 { | 	if len(r.Address) == 0 { | ||||||
| 		r.Address = "1.0.0.1:53" | 		r.Address = "1.1.1.1:53" | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	//nolint:prealloc |  | ||||||
| 	var records []*resolver.Record |  | ||||||
|  |  | ||||||
| 	// parsed an actual ip | 	// parsed an actual ip | ||||||
| 	if v := net.ParseIP(host); v != nil { | 	if v := net.ParseIP(host); v != nil { | ||||||
| 		records = append(records, &resolver.Record{ | 		rec := &resolver.Record{Address: net.JoinHostPort(host, port)} | ||||||
| 			Address: net.JoinHostPort(host, port), | 		return []*resolver.Record{rec}, nil | ||||||
| 		}) |  | ||||||
| 		return records, nil |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for _, q := range []uint16{dns.TypeA, dns.TypeAAAA} { | 	r.RLock() | ||||||
| 		m := new(dns.Msg) | 	goresolver := r.goresolver | ||||||
| 		m.SetQuestion(dns.Fqdn(host), q) | 	r.RUnlock() | ||||||
| 		rec, err := dns.ExchangeContext(context.Background(), m, r.Address) |  | ||||||
|  | 	if goresolver == nil { | ||||||
|  | 		r.Lock() | ||||||
|  | 		r.goresolver = &net.Resolver{ | ||||||
|  | 			Dial: func(ctx context.Context, network, address string) (net.Conn, error) { | ||||||
|  | 				d := net.Dialer{ | ||||||
|  | 					Timeout: time.Millisecond * time.Duration(100), | ||||||
|  | 				} | ||||||
|  | 				return d.DialContext(ctx, "udp", r.Address) | ||||||
|  | 			}, | ||||||
|  | 		} | ||||||
|  | 		r.Unlock() | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	addrs, err := goresolver.LookupIP(context.TODO(), "ip", host) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, err | 		return nil, err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 		var addr string | 	if len(addrs) == 0 { | ||||||
| 		for _, answer := range rec.Answer { | 		rec := &resolver.Record{Address: net.JoinHostPort(host, port)} | ||||||
| 			h := answer.Header() | 		return []*resolver.Record{rec}, nil | ||||||
| 			// check record type matches |  | ||||||
| 			switch h.Rrtype { |  | ||||||
| 			case dns.TypeA: |  | ||||||
| 				arec, _ := answer.(*dns.A) |  | ||||||
| 				addr = arec.A.String() |  | ||||||
| 			case dns.TypeAAAA: |  | ||||||
| 				arec, _ := answer.(*dns.AAAA) |  | ||||||
| 				addr = arec.AAAA.String() |  | ||||||
| 			default: |  | ||||||
| 				continue |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 			// join resolved record with port | 	records := make([]*resolver.Record, 0, len(addrs)) | ||||||
| 			address := net.JoinHostPort(addr, port) | 	for _, addr := range addrs { | ||||||
| 			// append to record set |  | ||||||
| 		records = append(records, &resolver.Record{ | 		records = append(records, &resolver.Record{ | ||||||
| 				Address: address, | 			Address: net.JoinHostPort(addr.String(), port), | ||||||
| 			}) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// no records returned so just best effort it |  | ||||||
| 	if len(records) == 0 { |  | ||||||
| 		records = append(records, &resolver.Record{ |  | ||||||
| 			Address: net.JoinHostPort(host, port), |  | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										14
									
								
								resolver/dns/dns_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								resolver/dns/dns_test.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | |||||||
|  | package dns | ||||||
|  |  | ||||||
|  | import "testing" | ||||||
|  |  | ||||||
|  | func TestResolver(t *testing.T) { | ||||||
|  | 	r := &Resolver{} | ||||||
|  | 	recs, err := r.Resolve("unistack.org") | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if len(recs) < 1 { | ||||||
|  | 		t.Fatalf("records not resolved: %v", recs) | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -3,7 +3,6 @@ package roundrobin | |||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| 	"github.com/unistack-org/micro/v3/selector" | 	"github.com/unistack-org/micro/v3/selector" | ||||||
| ) | ) | ||||||
|  |  | ||||||
| @@ -19,18 +18,29 @@ func TestRoundRobin(t *testing.T) { | |||||||
| 	// By passing r1 and r2 first, it forces a set sequence of (r1 => r2 => r3 => r1) | 	// By passing r1 and r2 first, it forces a set sequence of (r1 => r2 => r3 => r1) | ||||||
|  |  | ||||||
| 	next, err := sel.Select([]string{r1}) | 	next, err := sel.Select([]string{r1}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
| 	r := next() | 	r := next() | ||||||
| 	assert.Nil(t, err, "Error should be nil") |  | ||||||
| 	assert.Equal(t, r1, r, "Expected route to be r1") | 	if r1 != r { | ||||||
|  | 		t.Fatal("Expected route to be r == r1") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	next, err = sel.Select([]string{r2}) | 	next, err = sel.Select([]string{r2}) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
| 	r = next() | 	r = next() | ||||||
| 	assert.Nil(t, err, "Error should be nil") | 	if r2 != r { | ||||||
| 	assert.Equal(t, r2, r, "Expected route to be r2") | 		t.Fatal("Expected route to be r2") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	routes := []string{r1, r2, r3} | 	routes := []string{r1, r2, r3} | ||||||
| 	next, err = sel.Select(routes) | 	next, err = sel.Select(routes) | ||||||
| 	assert.Nil(t, err, "Error should be nil") | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
| 	n1, n2, n3, n4 := next(), next(), next(), next() | 	n1, n2, n3, n4 := next(), next(), next(), next() | ||||||
|  |  | ||||||
| 	// start element is random but then it should loop through in order | 	// start element is random but then it should loop through in order | ||||||
| @@ -41,9 +51,19 @@ func TestRoundRobin(t *testing.T) { | |||||||
| 			break | 			break | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	assert.NotEqual(t, start, -1) | 	if start == -1 { | ||||||
| 	assert.Equal(t, routes[start], n1, "Unexpected route") | 		t.Fatalf("start == -1 %v %v", start, -1) | ||||||
| 	assert.Equal(t, routes[(start+1)%3], n2, "Unexpected route") | 	} | ||||||
| 	assert.Equal(t, routes[(start+2)%3], n3, "Unexpected route") | 	if routes[start] != n1 { | ||||||
| 	assert.Equal(t, routes[(start+3)%3], n4, "Unexpected route") | 		t.Fatal("Unexpected route") | ||||||
|  | 	} | ||||||
|  | 	if routes[(start+1)%3] != n2 { | ||||||
|  | 		t.Fatal("Unexpected route") | ||||||
|  | 	} | ||||||
|  | 	if routes[(start+2)%3] != n3 { | ||||||
|  | 		t.Fatal("Unexpected route") | ||||||
|  | 	} | ||||||
|  | 	if routes[(start+3)%3] != n4 { | ||||||
|  | 		t.Fatal("Unexpected route") | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,8 +2,6 @@ package selector | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| // Tests runs all the tests against a selector to ensure the implementations are consistent | // Tests runs all the tests against a selector to ensure the implementations are consistent | ||||||
| @@ -14,19 +12,27 @@ func Tests(t *testing.T, s Selector) { | |||||||
| 	t.Run("Select", func(t *testing.T) { | 	t.Run("Select", func(t *testing.T) { | ||||||
| 		t.Run("NoRoutes", func(t *testing.T) { | 		t.Run("NoRoutes", func(t *testing.T) { | ||||||
| 			_, err := s.Select([]string{}) | 			_, err := s.Select([]string{}) | ||||||
| 			assert.Equal(t, ErrNoneAvailable, err, "Expected error to be none available") | 			if err != ErrNoneAvailable { | ||||||
|  | 				t.Fatal("Expected error to be none available") | ||||||
|  | 			} | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		t.Run("OneRoute", func(t *testing.T) { | 		t.Run("OneRoute", func(t *testing.T) { | ||||||
| 			next, err := s.Select([]string{r1}) | 			next, err := s.Select([]string{r1}) | ||||||
|  | 			if err != nil { | ||||||
|  | 				t.Fatal("Error should be nil") | ||||||
|  | 			} | ||||||
| 			srv := next() | 			srv := next() | ||||||
| 			assert.Nil(t, err, "Error should be nil") | 			if r1 != srv { | ||||||
| 			assert.Equal(t, r1, srv, "Expected the route to be returned") | 				t.Fatal("Expected the route to be returned") | ||||||
|  | 			} | ||||||
| 		}) | 		}) | ||||||
|  |  | ||||||
| 		t.Run("MultipleRoutes", func(t *testing.T) { | 		t.Run("MultipleRoutes", func(t *testing.T) { | ||||||
| 			next, err := s.Select([]string{r1, r2}) | 			next, err := s.Select([]string{r1, r2}) | ||||||
| 			assert.Nil(t, err, "Error should be nil") | 			if err != nil { | ||||||
|  | 				t.Fatal("Error should be nil") | ||||||
|  | 			} | ||||||
| 			srv := next() | 			srv := next() | ||||||
| 			if srv != r1 && srv != r2 { | 			if srv != r1 && srv != r2 { | ||||||
| 				t.Errorf("Expected the route to be one of the inputs") | 				t.Errorf("Expected the route to be one of the inputs") | ||||||
| @@ -35,11 +41,14 @@ func Tests(t *testing.T, s Selector) { | |||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("Record", func(t *testing.T) { | 	t.Run("Record", func(t *testing.T) { | ||||||
| 		err := s.Record(r1, nil) | 		if err := s.Record(r1, nil); err != nil { | ||||||
| 		assert.Nil(t, err, "Expected the error to be nil") | 			t.Fatal("Expected the error to be nil") | ||||||
|  | 		} | ||||||
| 	}) | 	}) | ||||||
|  |  | ||||||
| 	t.Run("String", func(t *testing.T) { | 	t.Run("String", func(t *testing.T) { | ||||||
| 		assert.NotEmpty(t, s.String(), "String returned a blank string") | 		if s.String() == "" { | ||||||
|  | 			t.Fatal("String returned a blank string") | ||||||
|  | 		} | ||||||
| 	}) | 	}) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ package store_test | |||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"os" |  | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| @@ -53,9 +52,6 @@ func TestMemoryNamespacePrefix(t *testing.T) { | |||||||
|  |  | ||||||
| func basictest(s store.Store, t *testing.T) { | func basictest(s store.Store, t *testing.T) { | ||||||
| 	ctx := context.Background() | 	ctx := context.Background() | ||||||
| 	if len(os.Getenv("IN_TRAVIS_CI")) == 0 { |  | ||||||
| 		t.Logf("Testing store %s, with options %#+v\n", s.String(), s.Options()) |  | ||||||
| 	} |  | ||||||
| 	// Read and Write an expiring Record | 	// Read and Write an expiring Record | ||||||
| 	if err := s.Write(ctx, "Hello", "World", store.WriteTTL(time.Millisecond*100)); err != nil { | 	if err := s.Write(ctx, "Hello", "World", store.WriteTTL(time.Millisecond*100)); err != nil { | ||||||
| 		t.Error(err) | 		t.Error(err) | ||||||
| @@ -71,5 +67,7 @@ func basictest(s store.Store, t *testing.T) { | |||||||
| 		t.Errorf("Expected %# v, got %# v", store.ErrNotFound, err) | 		t.Errorf("Expected %# v, got %# v", store.ErrNotFound, err) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	s.Disconnect(ctx) // reset the store | 	if err := s.Disconnect(ctx); err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -60,10 +60,9 @@ func NewRoundTripper(opts ...Option) http.RoundTripper { | |||||||
| // RequestToContext puts the `Authorization` header bearer token into context | // RequestToContext puts the `Authorization` header bearer token into context | ||||||
| // so calls to services will be authorized. | // so calls to services will be authorized. | ||||||
| func RequestToContext(r *http.Request) context.Context { | func RequestToContext(r *http.Request) context.Context { | ||||||
| 	ctx := context.Background() | 	md := metadata.New(len(r.Header)) | ||||||
| 	md := make(metadata.Metadata) |  | ||||||
| 	for k, v := range r.Header { | 	for k, v := range r.Header { | ||||||
| 		md[k] = strings.Join(v, ",") | 		md.Set(k, strings.Join(v, ",")) | ||||||
| 	} | 	} | ||||||
| 	return metadata.NewContext(ctx, md) | 	return metadata.NewIncomingContext(r.Context(), md) | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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 |  | ||||||
| } |  | ||||||
							
								
								
									
										23
									
								
								util/mdns/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								util/mdns/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,23 +0,0 @@ | |||||||
| # Compiled Object files, Static and Dynamic libs (Shared Objects) |  | ||||||
| *.o |  | ||||||
| *.a |  | ||||||
| *.so |  | ||||||
|  |  | ||||||
| # Folders |  | ||||||
| _obj |  | ||||||
| _test |  | ||||||
|  |  | ||||||
| # Architecture specific extensions/prefixes |  | ||||||
| *.[568vq] |  | ||||||
| [568vq].out |  | ||||||
|  |  | ||||||
| *.cgo1.go |  | ||||||
| *.cgo2.c |  | ||||||
| _cgo_defun.c |  | ||||||
| _cgo_gotypes.go |  | ||||||
| _cgo_export.* |  | ||||||
|  |  | ||||||
| _testmain.go |  | ||||||
|  |  | ||||||
| *.exe |  | ||||||
| *.test |  | ||||||
| @@ -1,511 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
| 	"log" |  | ||||||
| 	"net" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| 	"golang.org/x/net/ipv4" |  | ||||||
| 	"golang.org/x/net/ipv6" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // ServiceEntry is returned after we query for a service |  | ||||||
| type ServiceEntry struct { |  | ||||||
| 	Name       string |  | ||||||
| 	Host       string |  | ||||||
| 	AddrV4     net.IP |  | ||||||
| 	AddrV6     net.IP |  | ||||||
| 	Port       int |  | ||||||
| 	Info       string |  | ||||||
| 	InfoFields []string |  | ||||||
| 	TTL        int |  | ||||||
| 	Type       uint16 |  | ||||||
|  |  | ||||||
| 	Addr net.IP // @Deprecated |  | ||||||
|  |  | ||||||
| 	hasTXT bool |  | ||||||
| 	sent   bool |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // complete is used to check if we have all the info we need |  | ||||||
| func (s *ServiceEntry) complete() bool { |  | ||||||
|  |  | ||||||
| 	return (len(s.AddrV4) > 0 || len(s.AddrV6) > 0 || len(s.Addr) > 0) && s.Port != 0 && s.hasTXT |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // QueryParam is used to customize how a Lookup is performed |  | ||||||
| type QueryParam struct { |  | ||||||
| 	Service             string               // Service to lookup |  | ||||||
| 	Domain              string               // Lookup domain, default "local" |  | ||||||
| 	Type                uint16               // Lookup type, defaults to dns.TypePTR |  | ||||||
| 	Context             context.Context      // Context |  | ||||||
| 	Timeout             time.Duration        // Lookup timeout, default 1 second. Ignored if Context is provided |  | ||||||
| 	Interface           *net.Interface       // Multicast interface to use |  | ||||||
| 	Entries             chan<- *ServiceEntry // Entries Channel |  | ||||||
| 	WantUnicastResponse bool                 // Unicast response desired, as per 5.4 in RFC |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // DefaultParams is used to return a default set of QueryParam's |  | ||||||
| func DefaultParams(service string) *QueryParam { |  | ||||||
| 	return &QueryParam{ |  | ||||||
| 		Service:             service, |  | ||||||
| 		Domain:              "local", |  | ||||||
| 		Timeout:             time.Second, |  | ||||||
| 		Entries:             make(chan *ServiceEntry), |  | ||||||
| 		WantUnicastResponse: false, // TODO(reddaly): Change this default. |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Query looks up a given service, in a domain, waiting at most |  | ||||||
| // for a timeout before finishing the query. The results are streamed |  | ||||||
| // to a channel. Sends will not block, so clients should make sure to |  | ||||||
| // either read or buffer. |  | ||||||
| func Query(params *QueryParam) error { |  | ||||||
| 	// Create a new client |  | ||||||
| 	client, err := newClient() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer client.Close() |  | ||||||
|  |  | ||||||
| 	// Set the multicast interface |  | ||||||
| 	if params.Interface != nil { |  | ||||||
| 		if err := client.setInterface(params.Interface, false); err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Ensure defaults are set |  | ||||||
| 	if params.Domain == "" { |  | ||||||
| 		params.Domain = "local" |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if params.Context == nil { |  | ||||||
| 		var cancel context.CancelFunc |  | ||||||
| 		if params.Timeout == 0 { |  | ||||||
| 			params.Timeout = time.Second |  | ||||||
| 		} |  | ||||||
| 		params.Context, cancel = context.WithTimeout(context.Background(), params.Timeout) |  | ||||||
| 		defer cancel() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return err |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Run the query |  | ||||||
| 	return client.query(params) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Listen listens indefinitely for multicast updates |  | ||||||
| func Listen(entries chan<- *ServiceEntry, exit chan struct{}) error { |  | ||||||
| 	// Create a new client |  | ||||||
| 	client, err := newClient() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	defer client.Close() |  | ||||||
|  |  | ||||||
| 	client.setInterface(nil, true) |  | ||||||
|  |  | ||||||
| 	// Start listening for response packets |  | ||||||
| 	msgCh := make(chan *dns.Msg, 32) |  | ||||||
|  |  | ||||||
| 	go client.recv(client.ipv4UnicastConn, msgCh) |  | ||||||
| 	go client.recv(client.ipv6UnicastConn, msgCh) |  | ||||||
| 	go client.recv(client.ipv4MulticastConn, msgCh) |  | ||||||
| 	go client.recv(client.ipv6MulticastConn, msgCh) |  | ||||||
|  |  | ||||||
| 	ip := make(map[string]*ServiceEntry) |  | ||||||
|  |  | ||||||
| loop: |  | ||||||
| 	for { |  | ||||||
| 		select { |  | ||||||
| 		case <-exit: |  | ||||||
| 			break loop |  | ||||||
| 		case <-client.closedCh: |  | ||||||
| 			break loop |  | ||||||
| 		case m := <-msgCh: |  | ||||||
| 			e := messageToEntry(m, ip) |  | ||||||
| 			if e == nil { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Check if this entry is complete |  | ||||||
| 			if e.complete() { |  | ||||||
| 				if e.sent { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
| 				e.sent = true |  | ||||||
| 				entries <- e |  | ||||||
| 				ip = make(map[string]*ServiceEntry) |  | ||||||
| 			} else { |  | ||||||
| 				// Fire off a node specific query |  | ||||||
| 				m := new(dns.Msg) |  | ||||||
| 				m.SetQuestion(e.Name, dns.TypePTR) |  | ||||||
| 				m.RecursionDesired = false |  | ||||||
| 				if err := client.sendQuery(m); err != nil { |  | ||||||
| 					log.Printf("[ERR] mdns: Failed to query instance %s: %v", e.Name, err) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Lookup is the same as Query, however it uses all the default parameters |  | ||||||
| func Lookup(service string, entries chan<- *ServiceEntry) error { |  | ||||||
| 	params := DefaultParams(service) |  | ||||||
| 	params.Entries = entries |  | ||||||
| 	return Query(params) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Client provides a query interface that can be used to |  | ||||||
| // search for service providers using mDNS |  | ||||||
| type client struct { |  | ||||||
| 	ipv4UnicastConn *net.UDPConn |  | ||||||
| 	ipv6UnicastConn *net.UDPConn |  | ||||||
|  |  | ||||||
| 	ipv4MulticastConn *net.UDPConn |  | ||||||
| 	ipv6MulticastConn *net.UDPConn |  | ||||||
|  |  | ||||||
| 	closed    bool |  | ||||||
| 	closedCh  chan struct{} // TODO(reddaly): This doesn't appear to be used. |  | ||||||
| 	closeLock sync.Mutex |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewClient creates a new mdns Client that can be used to query |  | ||||||
| // for records |  | ||||||
| func newClient() (*client, error) { |  | ||||||
| 	// TODO(reddaly): At least attempt to bind to the port required in the spec. |  | ||||||
| 	// Create a IPv4 listener |  | ||||||
| 	uconn4, err4 := net.ListenUDP("udp4", &net.UDPAddr{IP: net.IPv4zero, Port: 0}) |  | ||||||
| 	uconn6, err6 := net.ListenUDP("udp6", &net.UDPAddr{IP: net.IPv6zero, Port: 0}) |  | ||||||
| 	if err4 != nil && err6 != nil { |  | ||||||
| 		log.Printf("[ERR] mdns: Failed to bind to udp port: %v %v", err4, err6) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if uconn4 == nil && uconn6 == nil { |  | ||||||
| 		return nil, fmt.Errorf("failed to bind to any unicast udp port") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if uconn4 == nil { |  | ||||||
| 		uconn4 = &net.UDPConn{} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if uconn6 == nil { |  | ||||||
| 		uconn6 = &net.UDPConn{} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	mconn4, err4 := net.ListenUDP("udp4", mdnsWildcardAddrIPv4) |  | ||||||
| 	mconn6, err6 := net.ListenUDP("udp6", mdnsWildcardAddrIPv6) |  | ||||||
| 	if err4 != nil && err6 != nil { |  | ||||||
| 		log.Printf("[ERR] mdns: Failed to bind to udp port: %v %v", err4, err6) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if mconn4 == nil && mconn6 == nil { |  | ||||||
| 		return nil, fmt.Errorf("failed to bind to any multicast udp port") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if mconn4 == nil { |  | ||||||
| 		mconn4 = &net.UDPConn{} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if mconn6 == nil { |  | ||||||
| 		mconn6 = &net.UDPConn{} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	p1 := ipv4.NewPacketConn(mconn4) |  | ||||||
| 	p2 := ipv6.NewPacketConn(mconn6) |  | ||||||
| 	p1.SetMulticastLoopback(true) |  | ||||||
| 	p2.SetMulticastLoopback(true) |  | ||||||
|  |  | ||||||
| 	ifaces, err := net.Interfaces() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return nil, err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	var errCount1, errCount2 int |  | ||||||
|  |  | ||||||
| 	for _, iface := range ifaces { |  | ||||||
| 		if err := p1.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { |  | ||||||
| 			errCount1++ |  | ||||||
| 		} |  | ||||||
| 		if err := p2.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { |  | ||||||
| 			errCount2++ |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(ifaces) == errCount1 && len(ifaces) == errCount2 { |  | ||||||
| 		return nil, fmt.Errorf("Failed to join multicast group on all interfaces!") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	c := &client{ |  | ||||||
| 		ipv4MulticastConn: mconn4, |  | ||||||
| 		ipv6MulticastConn: mconn6, |  | ||||||
| 		ipv4UnicastConn:   uconn4, |  | ||||||
| 		ipv6UnicastConn:   uconn6, |  | ||||||
| 		closedCh:          make(chan struct{}), |  | ||||||
| 	} |  | ||||||
| 	return c, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Close is used to cleanup the client |  | ||||||
| func (c *client) Close() error { |  | ||||||
| 	c.closeLock.Lock() |  | ||||||
| 	defer c.closeLock.Unlock() |  | ||||||
|  |  | ||||||
| 	if c.closed { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	c.closed = true |  | ||||||
|  |  | ||||||
| 	close(c.closedCh) |  | ||||||
|  |  | ||||||
| 	if c.ipv4UnicastConn != nil { |  | ||||||
| 		c.ipv4UnicastConn.Close() |  | ||||||
| 	} |  | ||||||
| 	if c.ipv6UnicastConn != nil { |  | ||||||
| 		c.ipv6UnicastConn.Close() |  | ||||||
| 	} |  | ||||||
| 	if c.ipv4MulticastConn != nil { |  | ||||||
| 		c.ipv4MulticastConn.Close() |  | ||||||
| 	} |  | ||||||
| 	if c.ipv6MulticastConn != nil { |  | ||||||
| 		c.ipv6MulticastConn.Close() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // setInterface is used to set the query interface, uses system |  | ||||||
| // default if not provided |  | ||||||
| func (c *client) setInterface(iface *net.Interface, loopback bool) error { |  | ||||||
| 	p := ipv4.NewPacketConn(c.ipv4UnicastConn) |  | ||||||
| 	if err := p.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	p2 := ipv6.NewPacketConn(c.ipv6UnicastConn) |  | ||||||
| 	if err := p2.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	p = ipv4.NewPacketConn(c.ipv4MulticastConn) |  | ||||||
| 	if err := p.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	p2 = ipv6.NewPacketConn(c.ipv6MulticastConn) |  | ||||||
| 	if err := p2.JoinGroup(iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if loopback { |  | ||||||
| 		p.SetMulticastLoopback(true) |  | ||||||
| 		p2.SetMulticastLoopback(true) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // query is used to perform a lookup and stream results |  | ||||||
| func (c *client) query(params *QueryParam) error { |  | ||||||
| 	// Create the service name |  | ||||||
| 	serviceAddr := fmt.Sprintf("%s.%s.", trimDot(params.Service), trimDot(params.Domain)) |  | ||||||
|  |  | ||||||
| 	// Start listening for response packets |  | ||||||
| 	msgCh := make(chan *dns.Msg, 32) |  | ||||||
| 	go c.recv(c.ipv4UnicastConn, msgCh) |  | ||||||
| 	go c.recv(c.ipv6UnicastConn, msgCh) |  | ||||||
| 	go c.recv(c.ipv4MulticastConn, msgCh) |  | ||||||
| 	go c.recv(c.ipv6MulticastConn, msgCh) |  | ||||||
|  |  | ||||||
| 	// Send the query |  | ||||||
| 	m := new(dns.Msg) |  | ||||||
| 	if params.Type == dns.TypeNone { |  | ||||||
| 		m.SetQuestion(serviceAddr, dns.TypePTR) |  | ||||||
| 	} else { |  | ||||||
| 		m.SetQuestion(serviceAddr, params.Type) |  | ||||||
| 	} |  | ||||||
| 	// RFC 6762, section 18.12.  Repurposing of Top Bit of qclass in Question |  | ||||||
| 	// Section |  | ||||||
| 	// |  | ||||||
| 	// In the Question Section of a Multicast DNS query, the top bit of the qclass |  | ||||||
| 	// field is used to indicate that unicast responses are preferred for this |  | ||||||
| 	// particular question.  (See Section 5.4.) |  | ||||||
| 	if params.WantUnicastResponse { |  | ||||||
| 		m.Question[0].Qclass |= 1 << 15 |  | ||||||
| 	} |  | ||||||
| 	m.RecursionDesired = false |  | ||||||
| 	if err := c.sendQuery(m); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Map the in-progress responses |  | ||||||
| 	inprogress := make(map[string]*ServiceEntry) |  | ||||||
|  |  | ||||||
| 	for { |  | ||||||
| 		select { |  | ||||||
| 		case resp := <-msgCh: |  | ||||||
| 			inp := messageToEntry(resp, inprogress) |  | ||||||
|  |  | ||||||
| 			if inp == nil { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			if len(resp.Question) == 0 || resp.Question[0].Name != m.Question[0].Name { |  | ||||||
| 				// discard anything which we've not asked for |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Check if this entry is complete |  | ||||||
| 			if inp.complete() { |  | ||||||
| 				if inp.sent { |  | ||||||
| 					continue |  | ||||||
| 				} |  | ||||||
|  |  | ||||||
| 				inp.sent = true |  | ||||||
| 				select { |  | ||||||
| 				case params.Entries <- inp: |  | ||||||
| 				case <-params.Context.Done(): |  | ||||||
| 					return nil |  | ||||||
| 				} |  | ||||||
| 			} else { |  | ||||||
| 				// Fire off a node specific query |  | ||||||
| 				m := new(dns.Msg) |  | ||||||
| 				m.SetQuestion(inp.Name, inp.Type) |  | ||||||
| 				m.RecursionDesired = false |  | ||||||
| 				if err := c.sendQuery(m); err != nil { |  | ||||||
| 					log.Printf("[ERR] mdns: Failed to query instance %s: %v", inp.Name, err) |  | ||||||
| 				} |  | ||||||
| 			} |  | ||||||
| 		case <-params.Context.Done(): |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // sendQuery is used to multicast a query out |  | ||||||
| func (c *client) sendQuery(q *dns.Msg) error { |  | ||||||
| 	buf, err := q.Pack() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if c.ipv4UnicastConn != nil { |  | ||||||
| 		c.ipv4UnicastConn.WriteToUDP(buf, ipv4Addr) |  | ||||||
| 	} |  | ||||||
| 	if c.ipv6UnicastConn != nil { |  | ||||||
| 		c.ipv6UnicastConn.WriteToUDP(buf, ipv6Addr) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // recv is used to receive until we get a shutdown |  | ||||||
| func (c *client) recv(l *net.UDPConn, msgCh chan *dns.Msg) { |  | ||||||
| 	if l == nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	buf := make([]byte, 65536) |  | ||||||
| 	for { |  | ||||||
| 		c.closeLock.Lock() |  | ||||||
| 		if c.closed { |  | ||||||
| 			c.closeLock.Unlock() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		c.closeLock.Unlock() |  | ||||||
| 		n, err := l.Read(buf) |  | ||||||
| 		if err != nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		msg := new(dns.Msg) |  | ||||||
| 		if err := msg.Unpack(buf[:n]); err != nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		select { |  | ||||||
| 		case msgCh <- msg: |  | ||||||
| 		case <-c.closedCh: |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // ensureName is used to ensure the named node is in progress |  | ||||||
| func ensureName(inprogress map[string]*ServiceEntry, name string, typ uint16) *ServiceEntry { |  | ||||||
| 	if inp, ok := inprogress[name]; ok { |  | ||||||
| 		return inp |  | ||||||
| 	} |  | ||||||
| 	inp := &ServiceEntry{ |  | ||||||
| 		Name: name, |  | ||||||
| 		Type: typ, |  | ||||||
| 	} |  | ||||||
| 	inprogress[name] = inp |  | ||||||
| 	return inp |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // alias is used to setup an alias between two entries |  | ||||||
| func alias(inprogress map[string]*ServiceEntry, src, dst string, typ uint16) { |  | ||||||
| 	srcEntry := ensureName(inprogress, src, typ) |  | ||||||
| 	inprogress[dst] = srcEntry |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func messageToEntry(m *dns.Msg, inprogress map[string]*ServiceEntry) *ServiceEntry { |  | ||||||
| 	var inp *ServiceEntry |  | ||||||
|  |  | ||||||
| 	for _, answer := range append(m.Answer, m.Extra...) { |  | ||||||
| 		// TODO(reddaly): Check that response corresponds to serviceAddr? |  | ||||||
| 		switch rr := answer.(type) { |  | ||||||
| 		case *dns.PTR: |  | ||||||
| 			// Create new entry for this |  | ||||||
| 			inp = ensureName(inprogress, rr.Ptr, rr.Hdr.Rrtype) |  | ||||||
| 			if inp.complete() { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 		case *dns.SRV: |  | ||||||
| 			// Check for a target mismatch |  | ||||||
| 			if rr.Target != rr.Hdr.Name { |  | ||||||
| 				alias(inprogress, rr.Hdr.Name, rr.Target, rr.Hdr.Rrtype) |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			// Get the port |  | ||||||
| 			inp = ensureName(inprogress, rr.Hdr.Name, rr.Hdr.Rrtype) |  | ||||||
| 			if inp.complete() { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			inp.Host = rr.Target |  | ||||||
| 			inp.Port = int(rr.Port) |  | ||||||
| 		case *dns.TXT: |  | ||||||
| 			// Pull out the txt |  | ||||||
| 			inp = ensureName(inprogress, rr.Hdr.Name, rr.Hdr.Rrtype) |  | ||||||
| 			if inp.complete() { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			inp.Info = strings.Join(rr.Txt, "|") |  | ||||||
| 			inp.InfoFields = rr.Txt |  | ||||||
| 			inp.hasTXT = true |  | ||||||
| 		case *dns.A: |  | ||||||
| 			// Pull out the IP |  | ||||||
| 			inp = ensureName(inprogress, rr.Hdr.Name, rr.Hdr.Rrtype) |  | ||||||
| 			if inp.complete() { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			inp.Addr = rr.A // @Deprecated |  | ||||||
| 			inp.AddrV4 = rr.A |  | ||||||
| 		case *dns.AAAA: |  | ||||||
| 			// Pull out the IP |  | ||||||
| 			inp = ensureName(inprogress, rr.Hdr.Name, rr.Hdr.Rrtype) |  | ||||||
| 			if inp.complete() { |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
| 			inp.Addr = rr.AAAA // @Deprecated |  | ||||||
| 			inp.AddrV6 = rr.AAAA |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		if inp != nil { |  | ||||||
| 			inp.TTL = int(answer.Header().Ttl) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return inp |  | ||||||
| } |  | ||||||
| @@ -1,84 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import "github.com/miekg/dns" |  | ||||||
|  |  | ||||||
| // DNSSDService is a service that complies with the DNS-SD (RFC 6762) and MDNS |  | ||||||
| // (RFC 6762) specs for local, multicast-DNS-based discovery. |  | ||||||
| // |  | ||||||
| // DNSSDService implements the Zone interface and wraps an MDNSService instance. |  | ||||||
| // To deploy an mDNS service that is compliant with DNS-SD, it's recommended to |  | ||||||
| // register only the wrapped instance with the server. |  | ||||||
| // |  | ||||||
| // Example usage: |  | ||||||
| //     service := &mdns.DNSSDService{ |  | ||||||
| //       MDNSService: &mdns.MDNSService{ |  | ||||||
| // 	       Instance: "My Foobar Service", |  | ||||||
| // 	       Service: "_foobar._tcp", |  | ||||||
| // 	       Port:    8000, |  | ||||||
| //        } |  | ||||||
| //      } |  | ||||||
| //      server, err := mdns.NewServer(&mdns.Config{Zone: service}) |  | ||||||
| //      if err != nil { |  | ||||||
| //        log.Fatalf("Error creating server: %v", err) |  | ||||||
| //      } |  | ||||||
| //      defer server.Shutdown() |  | ||||||
| type DNSSDService struct { |  | ||||||
| 	MDNSService *MDNSService |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Records returns DNS records in response to a DNS question. |  | ||||||
| // |  | ||||||
| // This function returns the DNS response of the underlying MDNSService |  | ||||||
| // instance.  It also returns a PTR record for a request for " |  | ||||||
| // _services._dns-sd._udp.<Domain>", as described in section 9 of RFC 6763 |  | ||||||
| // ("Service Type Enumeration"), to allow browsing of the underlying MDNSService |  | ||||||
| // instance. |  | ||||||
| func (s *DNSSDService) Records(q dns.Question) []dns.RR { |  | ||||||
| 	var recs []dns.RR |  | ||||||
| 	if q.Name == "_services._dns-sd._udp."+s.MDNSService.Domain+"." { |  | ||||||
| 		recs = s.dnssdMetaQueryRecords(q) |  | ||||||
| 	} |  | ||||||
| 	return append(recs, s.MDNSService.Records(q)...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // dnssdMetaQueryRecords returns the DNS records in response to a "meta-query" |  | ||||||
| // issued to browse for DNS-SD services, as per section 9. of RFC6763. |  | ||||||
| // |  | ||||||
| // A meta-query has a name of the form "_services._dns-sd._udp.<Domain>" where |  | ||||||
| // Domain is a fully-qualified domain, such as "local." |  | ||||||
| func (s *DNSSDService) dnssdMetaQueryRecords(q dns.Question) []dns.RR { |  | ||||||
| 	// Intended behavior, as described in the RFC: |  | ||||||
| 	//     ...it may be useful for network administrators to find the list of |  | ||||||
| 	//     advertised service types on the network, even if those Service Names |  | ||||||
| 	//     are just opaque identifiers and not particularly informative in |  | ||||||
| 	//     isolation. |  | ||||||
| 	// |  | ||||||
| 	//     For this purpose, a special meta-query is defined.  A DNS query for PTR |  | ||||||
| 	//     records with the name "_services._dns-sd._udp.<Domain>" yields a set of |  | ||||||
| 	//     PTR records, where the rdata of each PTR record is the two-abel |  | ||||||
| 	//     <Service> name, plus the same domain, e.g., "_http._tcp.<Domain>". |  | ||||||
| 	//     Including the domain in the PTR rdata allows for slightly better name |  | ||||||
| 	//     compression in Unicast DNS responses, but only the first two labels are |  | ||||||
| 	//     relevant for the purposes of service type enumeration.  These two-label |  | ||||||
| 	//     service types can then be used to construct subsequent Service Instance |  | ||||||
| 	//     Enumeration PTR queries, in this <Domain> or others, to discover |  | ||||||
| 	//     instances of that service type. |  | ||||||
| 	return []dns.RR{ |  | ||||||
| 		&dns.PTR{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   q.Name, |  | ||||||
| 				Rrtype: dns.TypePTR, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    defaultTTL, |  | ||||||
| 			}, |  | ||||||
| 			Ptr: s.MDNSService.serviceAddr, |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Announcement returns DNS records that should be broadcast during the initial |  | ||||||
| // availability of the service, as described in section 8.3 of RFC 6762. |  | ||||||
| // TODO(reddaly): Add this when Announcement is added to the mdns.Zone interface. |  | ||||||
| //func (s *DNSSDService) Announcement() []dns.RR { |  | ||||||
| //	return s.MDNSService.Announcement() |  | ||||||
| //} |  | ||||||
| @@ -1,69 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"reflect" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| type mockMDNSService struct{} |  | ||||||
|  |  | ||||||
| func (s *mockMDNSService) Records(q dns.Question) []dns.RR { |  | ||||||
| 	return []dns.RR{ |  | ||||||
| 		&dns.PTR{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   "fakerecord", |  | ||||||
| 				Rrtype: dns.TypePTR, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    42, |  | ||||||
| 			}, |  | ||||||
| 			Ptr: "fake.local.", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *mockMDNSService) Announcement() []dns.RR { |  | ||||||
| 	return []dns.RR{ |  | ||||||
| 		&dns.PTR{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   "fakeannounce", |  | ||||||
| 				Rrtype: dns.TypePTR, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    42, |  | ||||||
| 			}, |  | ||||||
| 			Ptr: "fake.local.", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestDNSSDServiceRecords(t *testing.T) { |  | ||||||
| 	s := &DNSSDService{ |  | ||||||
| 		MDNSService: &MDNSService{ |  | ||||||
| 			serviceAddr: "_foobar._tcp.local.", |  | ||||||
| 			Domain:      "local", |  | ||||||
| 		}, |  | ||||||
| 	} |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:   "_services._dns-sd._udp.local.", |  | ||||||
| 		Qtype:  dns.TypePTR, |  | ||||||
| 		Qclass: dns.ClassINET, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if got, want := len(recs), 1; got != want { |  | ||||||
| 		t.Fatalf("s.Records(%v) returned %v records, want %v", q, got, want) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	want := dns.RR(&dns.PTR{ |  | ||||||
| 		Hdr: dns.RR_Header{ |  | ||||||
| 			Name:   "_services._dns-sd._udp.local.", |  | ||||||
| 			Rrtype: dns.TypePTR, |  | ||||||
| 			Class:  dns.ClassINET, |  | ||||||
| 			Ttl:    defaultTTL, |  | ||||||
| 		}, |  | ||||||
| 		Ptr: "_foobar._tcp.local.", |  | ||||||
| 	}) |  | ||||||
| 	if got := recs[0]; !reflect.DeepEqual(got, want) { |  | ||||||
| 		t.Errorf("s.Records()[0] = %v, want %v", got, want) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,527 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
| 	"math/rand" |  | ||||||
| 	"net" |  | ||||||
| 	"sync" |  | ||||||
| 	"sync/atomic" |  | ||||||
| 	"time" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| 	"github.com/unistack-org/micro/v3/logger" |  | ||||||
| 	"golang.org/x/net/ipv4" |  | ||||||
| 	"golang.org/x/net/ipv6" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| var ( |  | ||||||
| 	mdnsGroupIPv4 = net.ParseIP("224.0.0.251") |  | ||||||
| 	mdnsGroupIPv6 = net.ParseIP("ff02::fb") |  | ||||||
|  |  | ||||||
| 	// mDNS wildcard addresses |  | ||||||
| 	mdnsWildcardAddrIPv4 = &net.UDPAddr{ |  | ||||||
| 		IP:   net.ParseIP("224.0.0.0"), |  | ||||||
| 		Port: 5353, |  | ||||||
| 	} |  | ||||||
| 	mdnsWildcardAddrIPv6 = &net.UDPAddr{ |  | ||||||
| 		IP:   net.ParseIP("ff02::"), |  | ||||||
| 		Port: 5353, |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// mDNS endpoint addresses |  | ||||||
| 	ipv4Addr = &net.UDPAddr{ |  | ||||||
| 		IP:   mdnsGroupIPv4, |  | ||||||
| 		Port: 5353, |  | ||||||
| 	} |  | ||||||
| 	ipv6Addr = &net.UDPAddr{ |  | ||||||
| 		IP:   mdnsGroupIPv6, |  | ||||||
| 		Port: 5353, |  | ||||||
| 	} |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // GetMachineIP is a func which returns the outbound IP of this machine. |  | ||||||
| // Used by the server to determine whether to attempt send the response on a local address |  | ||||||
| type GetMachineIP func() net.IP |  | ||||||
|  |  | ||||||
| // Config is used to configure the mDNS server |  | ||||||
| type Config struct { |  | ||||||
| 	// Zone must be provided to support responding to queries |  | ||||||
| 	Zone Zone |  | ||||||
|  |  | ||||||
| 	// Iface if provided binds the multicast listener to the given |  | ||||||
| 	// interface. If not provided, the system default multicase interface |  | ||||||
| 	// is used. |  | ||||||
| 	Iface *net.Interface |  | ||||||
|  |  | ||||||
| 	// Port If it is not 0, replace the port 5353 with this port number. |  | ||||||
| 	Port int |  | ||||||
|  |  | ||||||
| 	// GetMachineIP is a function to return the IP of the local machine |  | ||||||
| 	GetMachineIP GetMachineIP |  | ||||||
| 	// LocalhostChecking if enabled asks the server to also send responses to 0.0.0.0 if the target IP |  | ||||||
| 	// is this host (as defined by GetMachineIP). Useful in case machine is on a VPN which blocks comms on non standard ports |  | ||||||
| 	LocalhostChecking bool |  | ||||||
|  |  | ||||||
| 	Context context.Context |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Server is an mDNS server used to listen for mDNS queries and respond if we |  | ||||||
| // have a matching local record |  | ||||||
| type Server struct { |  | ||||||
| 	config *Config |  | ||||||
|  |  | ||||||
| 	ipv4List *net.UDPConn |  | ||||||
| 	ipv6List *net.UDPConn |  | ||||||
|  |  | ||||||
| 	shutdown     bool |  | ||||||
| 	shutdownCh   chan struct{} |  | ||||||
| 	shutdownLock sync.Mutex |  | ||||||
| 	wg           sync.WaitGroup |  | ||||||
|  |  | ||||||
| 	outboundIP net.IP |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewServer is used to create a new mDNS server from a config |  | ||||||
| func NewServer(config *Config) (*Server, error) { |  | ||||||
| 	setCustomPort(config.Port) |  | ||||||
|  |  | ||||||
| 	// Create the listeners |  | ||||||
| 	// Create wildcard connections (because :5353 can be already taken by other apps) |  | ||||||
| 	ipv4List, _ := net.ListenUDP("udp4", mdnsWildcardAddrIPv4) |  | ||||||
| 	ipv6List, _ := net.ListenUDP("udp6", mdnsWildcardAddrIPv6) |  | ||||||
| 	if ipv4List == nil && ipv6List == nil { |  | ||||||
| 		return nil, fmt.Errorf("[ERR] mdns: Failed to bind to any udp port!") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if ipv4List == nil { |  | ||||||
| 		ipv4List = &net.UDPConn{} |  | ||||||
| 	} |  | ||||||
| 	if ipv6List == nil { |  | ||||||
| 		ipv6List = &net.UDPConn{} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Join multicast groups to receive announcements |  | ||||||
| 	p1 := ipv4.NewPacketConn(ipv4List) |  | ||||||
| 	p2 := ipv6.NewPacketConn(ipv6List) |  | ||||||
| 	p1.SetMulticastLoopback(true) |  | ||||||
| 	p2.SetMulticastLoopback(true) |  | ||||||
|  |  | ||||||
| 	if config.Iface != nil { |  | ||||||
| 		if err := p1.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		if err := p2.JoinGroup(config.Iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 	} else { |  | ||||||
| 		ifaces, err := net.Interfaces() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, err |  | ||||||
| 		} |  | ||||||
| 		errCount1, errCount2 := 0, 0 |  | ||||||
| 		for _, iface := range ifaces { |  | ||||||
| 			if err := p1.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv4}); err != nil { |  | ||||||
| 				errCount1++ |  | ||||||
| 			} |  | ||||||
| 			if err := p2.JoinGroup(&iface, &net.UDPAddr{IP: mdnsGroupIPv6}); err != nil { |  | ||||||
| 				errCount2++ |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		if len(ifaces) == errCount1 && len(ifaces) == errCount2 { |  | ||||||
| 			return nil, fmt.Errorf("Failed to join multicast group on all interfaces!") |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	ipFunc := getOutboundIP |  | ||||||
| 	if config.GetMachineIP != nil { |  | ||||||
| 		ipFunc = config.GetMachineIP |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s := &Server{ |  | ||||||
| 		config:     config, |  | ||||||
| 		ipv4List:   ipv4List, |  | ||||||
| 		ipv6List:   ipv6List, |  | ||||||
| 		shutdownCh: make(chan struct{}), |  | ||||||
| 		outboundIP: ipFunc(), |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if s.config.Context == nil { |  | ||||||
| 		s.config.Context = context.Background() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	go s.recv(s.ipv4List) |  | ||||||
| 	go s.recv(s.ipv6List) |  | ||||||
|  |  | ||||||
| 	s.wg.Add(1) |  | ||||||
| 	go s.probe() |  | ||||||
|  |  | ||||||
| 	return s, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Shutdown is used to shutdown the listener |  | ||||||
| func (s *Server) Shutdown() error { |  | ||||||
| 	s.shutdownLock.Lock() |  | ||||||
| 	defer s.shutdownLock.Unlock() |  | ||||||
|  |  | ||||||
| 	if s.shutdown { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s.shutdown = true |  | ||||||
| 	close(s.shutdownCh) |  | ||||||
| 	if err := s.unregister(); err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if s.ipv4List != nil { |  | ||||||
| 		s.ipv4List.Close() |  | ||||||
| 	} |  | ||||||
| 	if s.ipv6List != nil { |  | ||||||
| 		s.ipv6List.Close() |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	s.wg.Wait() |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // recv is a long running routine to receive packets from an interface |  | ||||||
| func (s *Server) recv(c *net.UDPConn) { |  | ||||||
| 	if c == nil { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
| 	buf := make([]byte, 65536) |  | ||||||
| 	for { |  | ||||||
| 		s.shutdownLock.Lock() |  | ||||||
| 		if s.shutdown { |  | ||||||
| 			s.shutdownLock.Unlock() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 		s.shutdownLock.Unlock() |  | ||||||
| 		n, from, err := c.ReadFrom(buf) |  | ||||||
| 		if err != nil { |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
| 		if err := s.parsePacket(buf[:n], from); err != nil { |  | ||||||
| 			logger.Errorf(s.config.Context, "[ERR] mdns: Failed to handle query: %v", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // parsePacket is used to parse an incoming packet |  | ||||||
| func (s *Server) parsePacket(packet []byte, from net.Addr) error { |  | ||||||
| 	var msg dns.Msg |  | ||||||
| 	if err := msg.Unpack(packet); err != nil { |  | ||||||
| 		logger.Errorf(s.config.Context, "[ERR] mdns: Failed to unpack packet: %v", err) |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	// TODO: This is a bit of a hack |  | ||||||
| 	// We decided to ignore some mDNS answers for the time being |  | ||||||
| 	// See: https://tools.ietf.org/html/rfc6762#section-7.2 |  | ||||||
| 	msg.Truncated = false |  | ||||||
| 	return s.handleQuery(&msg, from) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // handleQuery is used to handle an incoming query |  | ||||||
| func (s *Server) handleQuery(query *dns.Msg, from net.Addr) error { |  | ||||||
| 	if query.Opcode != dns.OpcodeQuery { |  | ||||||
| 		// "In both multicast query and multicast response messages, the OPCODE MUST |  | ||||||
| 		// be zero on transmission (only standard queries are currently supported |  | ||||||
| 		// over multicast).  Multicast DNS messages received with an OPCODE other |  | ||||||
| 		// than zero MUST be silently ignored."  Note: OpcodeQuery == 0 |  | ||||||
| 		return fmt.Errorf("mdns: received query with non-zero Opcode %v: %v", query.Opcode, *query) |  | ||||||
| 	} |  | ||||||
| 	if query.Rcode != 0 { |  | ||||||
| 		// "In both multicast query and multicast response messages, the Response |  | ||||||
| 		// Code MUST be zero on transmission.  Multicast DNS messages received with |  | ||||||
| 		// non-zero Response Codes MUST be silently ignored." |  | ||||||
| 		return fmt.Errorf("mdns: received query with non-zero Rcode %v: %v", query.Rcode, *query) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// TODO(reddaly): Handle "TC (Truncated) Bit": |  | ||||||
| 	//    In query messages, if the TC bit is set, it means that additional |  | ||||||
| 	//    Known-Answer records may be following shortly.  A responder SHOULD |  | ||||||
| 	//    record this fact, and wait for those additional Known-Answer records, |  | ||||||
| 	//    before deciding whether to respond.  If the TC bit is clear, it means |  | ||||||
| 	//    that the querying host has no additional Known Answers. |  | ||||||
| 	if query.Truncated { |  | ||||||
| 		return fmt.Errorf("[ERR] mdns: support for DNS requests with high truncated bit not implemented: %v", *query) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	unicastAnswer := make([]dns.RR, 0, len(query.Question)) |  | ||||||
| 	multicastAnswer := make([]dns.RR, 0, len(query.Question)) |  | ||||||
|  |  | ||||||
| 	// Handle each question |  | ||||||
| 	for _, q := range query.Question { |  | ||||||
| 		mrecs, urecs := s.handleQuestion(q) |  | ||||||
| 		multicastAnswer = append(multicastAnswer, mrecs...) |  | ||||||
| 		unicastAnswer = append(unicastAnswer, urecs...) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// See section 18 of RFC 6762 for rules about DNS headers. |  | ||||||
| 	resp := func(unicast bool) *dns.Msg { |  | ||||||
| 		// 18.1: ID (Query Identifier) |  | ||||||
| 		// 0 for multicast response, query.Id for unicast response |  | ||||||
| 		id := uint16(0) |  | ||||||
| 		if unicast { |  | ||||||
| 			id = query.Id |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		var answer []dns.RR |  | ||||||
| 		if unicast { |  | ||||||
| 			answer = unicastAnswer |  | ||||||
| 		} else { |  | ||||||
| 			answer = multicastAnswer |  | ||||||
| 		} |  | ||||||
| 		if len(answer) == 0 { |  | ||||||
| 			return nil |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		return &dns.Msg{ |  | ||||||
| 			MsgHdr: dns.MsgHdr{ |  | ||||||
| 				Id: id, |  | ||||||
|  |  | ||||||
| 				// 18.2: QR (Query/Response) Bit - must be set to 1 in response. |  | ||||||
| 				Response: true, |  | ||||||
|  |  | ||||||
| 				// 18.3: OPCODE - must be zero in response (OpcodeQuery == 0) |  | ||||||
| 				Opcode: dns.OpcodeQuery, |  | ||||||
|  |  | ||||||
| 				// 18.4: AA (Authoritative Answer) Bit - must be set to 1 |  | ||||||
| 				Authoritative: true, |  | ||||||
|  |  | ||||||
| 				// The following fields must all be set to 0: |  | ||||||
| 				// 18.5: TC (TRUNCATED) Bit |  | ||||||
| 				// 18.6: RD (Recursion Desired) Bit |  | ||||||
| 				// 18.7: RA (Recursion Available) Bit |  | ||||||
| 				// 18.8: Z (Zero) Bit |  | ||||||
| 				// 18.9: AD (Authentic Data) Bit |  | ||||||
| 				// 18.10: CD (Checking Disabled) Bit |  | ||||||
| 				// 18.11: RCODE (Response Code) |  | ||||||
| 			}, |  | ||||||
| 			// 18.12 pertains to questions (handled by handleQuestion) |  | ||||||
| 			// 18.13 pertains to resource records (handled by handleQuestion) |  | ||||||
|  |  | ||||||
| 			// 18.14: Name Compression - responses should be compressed (though see |  | ||||||
| 			// caveats in the RFC), so set the Compress bit (part of the dns library |  | ||||||
| 			// API, not part of the DNS packet) to true. |  | ||||||
| 			Compress: true, |  | ||||||
| 			Question: query.Question, |  | ||||||
| 			Answer:   answer, |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if mresp := resp(false); mresp != nil { |  | ||||||
| 		if err := s.sendResponse(mresp, from); err != nil { |  | ||||||
| 			return fmt.Errorf("mdns: error sending multicast response: %v", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	if uresp := resp(true); uresp != nil { |  | ||||||
| 		if err := s.sendResponse(uresp, from); err != nil { |  | ||||||
| 			return fmt.Errorf("mdns: error sending unicast response: %v", err) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // handleQuestion is used to handle an incoming question |  | ||||||
| // |  | ||||||
| // The response to a question may be transmitted over multicast, unicast, or |  | ||||||
| // both.  The return values are DNS records for each transmission type. |  | ||||||
| func (s *Server) handleQuestion(q dns.Question) (multicastRecs, unicastRecs []dns.RR) { |  | ||||||
| 	records := s.config.Zone.Records(q) |  | ||||||
| 	if len(records) == 0 { |  | ||||||
| 		return nil, nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Handle unicast and multicast responses. |  | ||||||
| 	// TODO(reddaly): The decision about sending over unicast vs. multicast is not |  | ||||||
| 	// yet fully compliant with RFC 6762.  For example, the unicast bit should be |  | ||||||
| 	// ignored if the records in question are close to TTL expiration.  For now, |  | ||||||
| 	// we just use the unicast bit to make the decision, as per the spec: |  | ||||||
| 	//     RFC 6762, section 18.12.  Repurposing of Top Bit of qclass in Question |  | ||||||
| 	//     Section |  | ||||||
| 	// |  | ||||||
| 	//     In the Question Section of a Multicast DNS query, the top bit of the |  | ||||||
| 	//     qclass field is used to indicate that unicast responses are preferred |  | ||||||
| 	//     for this particular question.  (See Section 5.4.) |  | ||||||
| 	if q.Qclass&(1<<15) != 0 { |  | ||||||
| 		return nil, records |  | ||||||
| 	} |  | ||||||
| 	return records, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Server) probe() { |  | ||||||
| 	defer s.wg.Done() |  | ||||||
|  |  | ||||||
| 	sd, ok := s.config.Zone.(*MDNSService) |  | ||||||
| 	if !ok { |  | ||||||
| 		return |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	name := fmt.Sprintf("%s.%s.%s.", sd.Instance, trimDot(sd.Service), trimDot(sd.Domain)) |  | ||||||
|  |  | ||||||
| 	q := new(dns.Msg) |  | ||||||
| 	q.SetQuestion(name, dns.TypePTR) |  | ||||||
| 	q.RecursionDesired = false |  | ||||||
|  |  | ||||||
| 	srv := &dns.SRV{ |  | ||||||
| 		Hdr: dns.RR_Header{ |  | ||||||
| 			Name:   name, |  | ||||||
| 			Rrtype: dns.TypeSRV, |  | ||||||
| 			Class:  dns.ClassINET, |  | ||||||
| 			Ttl:    defaultTTL, |  | ||||||
| 		}, |  | ||||||
| 		Priority: 0, |  | ||||||
| 		Weight:   0, |  | ||||||
| 		Port:     uint16(sd.Port), |  | ||||||
| 		Target:   sd.HostName, |  | ||||||
| 	} |  | ||||||
| 	txt := &dns.TXT{ |  | ||||||
| 		Hdr: dns.RR_Header{ |  | ||||||
| 			Name:   name, |  | ||||||
| 			Rrtype: dns.TypeTXT, |  | ||||||
| 			Class:  dns.ClassINET, |  | ||||||
| 			Ttl:    defaultTTL, |  | ||||||
| 		}, |  | ||||||
| 		Txt: sd.TXT, |  | ||||||
| 	} |  | ||||||
| 	q.Ns = []dns.RR{srv, txt} |  | ||||||
|  |  | ||||||
| 	randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) |  | ||||||
|  |  | ||||||
| 	for i := 0; i < 3; i++ { |  | ||||||
| 		if err := s.SendMulticast(q); err != nil { |  | ||||||
| 			logger.Errorf(s.config.Context, "[ERR] mdns: failed to send probe: %v", err) |  | ||||||
| 		} |  | ||||||
| 		time.Sleep(time.Duration(randomizer.Intn(250)) * time.Millisecond) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	resp := new(dns.Msg) |  | ||||||
| 	resp.MsgHdr.Response = true |  | ||||||
|  |  | ||||||
| 	// set for query |  | ||||||
| 	q.SetQuestion(name, dns.TypeANY) |  | ||||||
|  |  | ||||||
| 	resp.Answer = append(resp.Answer, s.config.Zone.Records(q.Question[0])...) |  | ||||||
|  |  | ||||||
| 	// reset |  | ||||||
| 	q.SetQuestion(name, dns.TypePTR) |  | ||||||
|  |  | ||||||
| 	// From RFC6762 |  | ||||||
| 	//    The Multicast DNS responder MUST send at least two unsolicited |  | ||||||
| 	//    responses, one second apart. To provide increased robustness against |  | ||||||
| 	//    packet loss, a responder MAY send up to eight unsolicited responses, |  | ||||||
| 	//    provided that the interval between unsolicited responses increases by |  | ||||||
| 	//    at least a factor of two with every response sent. |  | ||||||
| 	timeout := 1 * time.Second |  | ||||||
| 	timer := time.NewTimer(timeout) |  | ||||||
| 	for i := 0; i < 3; i++ { |  | ||||||
| 		if err := s.SendMulticast(resp); err != nil { |  | ||||||
| 			logger.Errorf(s.config.Context, "[ERR] mdns: failed to send announcement: %v", err) |  | ||||||
| 		} |  | ||||||
| 		select { |  | ||||||
| 		case <-timer.C: |  | ||||||
| 			timeout *= 2 |  | ||||||
| 			timer.Reset(timeout) |  | ||||||
| 		case <-s.shutdownCh: |  | ||||||
| 			timer.Stop() |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // SendMulticast us used to send a multicast response packet |  | ||||||
| func (s *Server) SendMulticast(msg *dns.Msg) error { |  | ||||||
| 	buf, err := msg.Pack() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
| 	if s.ipv4List != nil { |  | ||||||
| 		s.ipv4List.WriteToUDP(buf, ipv4Addr) |  | ||||||
| 	} |  | ||||||
| 	if s.ipv6List != nil { |  | ||||||
| 		s.ipv6List.WriteToUDP(buf, ipv6Addr) |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // sendResponse is used to send a response packet |  | ||||||
| func (s *Server) sendResponse(resp *dns.Msg, from net.Addr) error { |  | ||||||
| 	// TODO(reddaly): Respect the unicast argument, and allow sending responses |  | ||||||
| 	// over multicast. |  | ||||||
| 	buf, err := resp.Pack() |  | ||||||
| 	if err != nil { |  | ||||||
| 		return err |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Determine the socket to send from |  | ||||||
| 	addr := from.(*net.UDPAddr) |  | ||||||
| 	conn := s.ipv4List |  | ||||||
| 	backupTarget := net.IPv4zero |  | ||||||
|  |  | ||||||
| 	if addr.IP.To4() == nil { |  | ||||||
| 		conn = s.ipv6List |  | ||||||
| 		backupTarget = net.IPv6zero |  | ||||||
| 	} |  | ||||||
| 	_, err = conn.WriteToUDP(buf, addr) |  | ||||||
| 	// If the address we're responding to is this machine then we can also attempt sending on 0.0.0.0 |  | ||||||
| 	// This covers the case where this machine is using a VPN and certain ports are blocked so the response never gets there |  | ||||||
| 	// Sending two responses is OK |  | ||||||
| 	if s.config.LocalhostChecking && addr.IP.Equal(s.outboundIP) { |  | ||||||
| 		// ignore any errors, this is best efforts |  | ||||||
| 		conn.WriteToUDP(buf, &net.UDPAddr{IP: backupTarget, Port: addr.Port}) |  | ||||||
| 	} |  | ||||||
| 	return err |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Server) unregister() error { |  | ||||||
| 	sd, ok := s.config.Zone.(*MDNSService) |  | ||||||
| 	if !ok { |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	atomic.StoreUint32(&sd.TTL, 0) |  | ||||||
| 	name := fmt.Sprintf("%s.%s.%s.", sd.Instance, trimDot(sd.Service), trimDot(sd.Domain)) |  | ||||||
|  |  | ||||||
| 	q := new(dns.Msg) |  | ||||||
| 	q.SetQuestion(name, dns.TypeANY) |  | ||||||
|  |  | ||||||
| 	resp := new(dns.Msg) |  | ||||||
| 	resp.MsgHdr.Response = true |  | ||||||
| 	resp.Answer = append(resp.Answer, s.config.Zone.Records(q.Question[0])...) |  | ||||||
|  |  | ||||||
| 	return s.SendMulticast(resp) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func setCustomPort(port int) { |  | ||||||
| 	if port != 0 { |  | ||||||
| 		if mdnsWildcardAddrIPv4.Port != port { |  | ||||||
| 			mdnsWildcardAddrIPv4.Port = port |  | ||||||
| 		} |  | ||||||
| 		if mdnsWildcardAddrIPv6.Port != port { |  | ||||||
| 			mdnsWildcardAddrIPv6.Port = port |  | ||||||
| 		} |  | ||||||
| 		if ipv4Addr.Port != port { |  | ||||||
| 			ipv4Addr.Port = port |  | ||||||
| 		} |  | ||||||
| 		if ipv6Addr.Port != port { |  | ||||||
| 			ipv6Addr.Port = port |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // getOutboundIP returns the IP address of this machine as seen when dialling out |  | ||||||
| func getOutboundIP() net.IP { |  | ||||||
| 	conn, err := net.Dial("udp", "8.8.8.8:80") |  | ||||||
| 	if err != nil { |  | ||||||
| 		// no net connectivity maybe so fallback |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| 	defer conn.Close() |  | ||||||
|  |  | ||||||
| 	localAddr := conn.LocalAddr().(*net.UDPAddr) |  | ||||||
|  |  | ||||||
| 	return localAddr.IP |  | ||||||
| } |  | ||||||
| @@ -1,61 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"testing" |  | ||||||
| 	"time" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func TestServer_StartStop(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	serv, err := NewServer(&Config{Zone: s, LocalhostChecking: true}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("err: %v", err) |  | ||||||
| 	} |  | ||||||
| 	defer serv.Shutdown() |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestServer_Lookup(t *testing.T) { |  | ||||||
| 	serv, err := NewServer(&Config{Zone: makeServiceWithServiceName(t, "_foobar._tcp"), LocalhostChecking: true}) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("err: %v", err) |  | ||||||
| 	} |  | ||||||
| 	defer serv.Shutdown() |  | ||||||
|  |  | ||||||
| 	entries := make(chan *ServiceEntry, 1) |  | ||||||
| 	found := false |  | ||||||
| 	doneCh := make(chan struct{}) |  | ||||||
| 	go func() { |  | ||||||
| 		select { |  | ||||||
| 		case e := <-entries: |  | ||||||
| 			if e.Name != "hostname._foobar._tcp.local." { |  | ||||||
| 				t.Fatalf("bad: %v", e) |  | ||||||
| 			} |  | ||||||
| 			if e.Port != 80 { |  | ||||||
| 				t.Fatalf("bad: %v", e) |  | ||||||
| 			} |  | ||||||
| 			if e.Info != "Local web server" { |  | ||||||
| 				t.Fatalf("bad: %v", e) |  | ||||||
| 			} |  | ||||||
| 			found = true |  | ||||||
|  |  | ||||||
| 		case <-time.After(80 * time.Millisecond): |  | ||||||
| 			t.Fatalf("timeout") |  | ||||||
| 		} |  | ||||||
| 		close(doneCh) |  | ||||||
| 	}() |  | ||||||
|  |  | ||||||
| 	params := &QueryParam{ |  | ||||||
| 		Service: "_foobar._tcp", |  | ||||||
| 		Domain:  "local", |  | ||||||
| 		Timeout: 50 * time.Millisecond, |  | ||||||
| 		Entries: entries, |  | ||||||
| 	} |  | ||||||
| 	err = Query(params) |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("err: %v", err) |  | ||||||
| 	} |  | ||||||
| 	<-doneCh |  | ||||||
| 	if !found { |  | ||||||
| 		t.Fatalf("record not found") |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,309 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"fmt" |  | ||||||
| 	"net" |  | ||||||
| 	"os" |  | ||||||
| 	"strings" |  | ||||||
| 	"sync/atomic" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| const ( |  | ||||||
| 	// defaultTTL is the default TTL value in returned DNS records in seconds. |  | ||||||
| 	defaultTTL = 120 |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Zone is the interface used to integrate with the server and |  | ||||||
| // to serve records dynamically |  | ||||||
| type Zone interface { |  | ||||||
| 	// Records returns DNS records in response to a DNS question. |  | ||||||
| 	Records(q dns.Question) []dns.RR |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // MDNSService is used to export a named service by implementing a Zone |  | ||||||
| type MDNSService struct { |  | ||||||
| 	Instance     string   // Instance name (e.g. "hostService name") |  | ||||||
| 	Service      string   // Service name (e.g. "_http._tcp.") |  | ||||||
| 	Domain       string   // If blank, assumes "local" |  | ||||||
| 	HostName     string   // Host machine DNS name (e.g. "mymachine.net.") |  | ||||||
| 	Port         int      // Service Port |  | ||||||
| 	IPs          []net.IP // IP addresses for the service's host |  | ||||||
| 	TXT          []string // Service TXT records |  | ||||||
| 	TTL          uint32 |  | ||||||
| 	serviceAddr  string // Fully qualified service address |  | ||||||
| 	instanceAddr string // Fully qualified instance address |  | ||||||
| 	enumAddr     string // _services._dns-sd._udp.<domain> |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // validateFQDN returns an error if the passed string is not a fully qualified |  | ||||||
| // hdomain name (more specifically, a hostname). |  | ||||||
| func validateFQDN(s string) error { |  | ||||||
| 	if len(s) == 0 { |  | ||||||
| 		return fmt.Errorf("FQDN must not be blank") |  | ||||||
| 	} |  | ||||||
| 	if s[len(s)-1] != '.' { |  | ||||||
| 		return fmt.Errorf("FQDN must end in period: %s", s) |  | ||||||
| 	} |  | ||||||
| 	// TODO(reddaly): Perform full validation. |  | ||||||
|  |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewMDNSService returns a new instance of MDNSService. |  | ||||||
| // |  | ||||||
| // If domain, hostName, or ips is set to the zero value, then a default value |  | ||||||
| // will be inferred from the operating system. |  | ||||||
| // |  | ||||||
| // TODO(reddaly): This interface may need to change to account for "unique |  | ||||||
| // record" conflict rules of the mDNS protocol.  Upon startup, the server should |  | ||||||
| // check to ensure that the instance name does not conflict with other instance |  | ||||||
| // names, and, if required, select a new name.  There may also be conflicting |  | ||||||
| // hostName A/AAAA records. |  | ||||||
| func NewMDNSService(instance, service, domain, hostName string, port int, ips []net.IP, txt []string) (*MDNSService, error) { |  | ||||||
| 	// Sanity check inputs |  | ||||||
| 	if instance == "" { |  | ||||||
| 		return nil, fmt.Errorf("missing service instance name") |  | ||||||
| 	} |  | ||||||
| 	if service == "" { |  | ||||||
| 		return nil, fmt.Errorf("missing service name") |  | ||||||
| 	} |  | ||||||
| 	if port == 0 { |  | ||||||
| 		return nil, fmt.Errorf("missing service port") |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Set default domain |  | ||||||
| 	if domain == "" { |  | ||||||
| 		domain = "local." |  | ||||||
| 	} |  | ||||||
| 	if err := validateFQDN(domain); err != nil { |  | ||||||
| 		return nil, fmt.Errorf("domain %q is not a fully-qualified domain name: %v", domain, err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	// Get host information if no host is specified. |  | ||||||
| 	if hostName == "" { |  | ||||||
| 		var err error |  | ||||||
| 		hostName, err = os.Hostname() |  | ||||||
| 		if err != nil { |  | ||||||
| 			return nil, fmt.Errorf("could not determine host: %v", err) |  | ||||||
| 		} |  | ||||||
| 		hostName = fmt.Sprintf("%s.", hostName) |  | ||||||
| 	} |  | ||||||
| 	if err := validateFQDN(hostName); err != nil { |  | ||||||
| 		return nil, fmt.Errorf("hostName %q is not a fully-qualified domain name: %v", hostName, err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if len(ips) == 0 { |  | ||||||
| 		var err error |  | ||||||
| 		ips, err = net.LookupIP(trimDot(hostName)) |  | ||||||
| 		if err != nil { |  | ||||||
| 			// Try appending the host domain suffix and lookup again |  | ||||||
| 			// (required for Linux-based hosts) |  | ||||||
| 			tmpHostName := fmt.Sprintf("%s%s", hostName, domain) |  | ||||||
|  |  | ||||||
| 			ips, err = net.LookupIP(trimDot(tmpHostName)) |  | ||||||
|  |  | ||||||
| 			if err != nil { |  | ||||||
| 				return nil, fmt.Errorf("could not determine host IP addresses for %s", hostName) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| 	for _, ip := range ips { |  | ||||||
| 		if ip.To4() == nil && ip.To16() == nil { |  | ||||||
| 			return nil, fmt.Errorf("invalid IP address in IPs list: %v", ip) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return &MDNSService{ |  | ||||||
| 		Instance:     instance, |  | ||||||
| 		Service:      service, |  | ||||||
| 		Domain:       domain, |  | ||||||
| 		HostName:     hostName, |  | ||||||
| 		Port:         port, |  | ||||||
| 		IPs:          ips, |  | ||||||
| 		TXT:          txt, |  | ||||||
| 		TTL:          defaultTTL, |  | ||||||
| 		serviceAddr:  fmt.Sprintf("%s.%s.", trimDot(service), trimDot(domain)), |  | ||||||
| 		instanceAddr: fmt.Sprintf("%s.%s.%s.", instance, trimDot(service), trimDot(domain)), |  | ||||||
| 		enumAddr:     fmt.Sprintf("_services._dns-sd._udp.%s.", trimDot(domain)), |  | ||||||
| 	}, nil |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // trimDot is used to trim the dots from the start or end of a string |  | ||||||
| func trimDot(s string) string { |  | ||||||
| 	return strings.Trim(s, ".") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // Records returns DNS records in response to a DNS question. |  | ||||||
| func (m *MDNSService) Records(q dns.Question) []dns.RR { |  | ||||||
| 	switch q.Name { |  | ||||||
| 	case m.enumAddr: |  | ||||||
| 		return m.serviceEnum(q) |  | ||||||
| 	case m.serviceAddr: |  | ||||||
| 		return m.serviceRecords(q) |  | ||||||
| 	case m.instanceAddr: |  | ||||||
| 		return m.instanceRecords(q) |  | ||||||
| 	case m.HostName: |  | ||||||
| 		if q.Qtype == dns.TypeA || q.Qtype == dns.TypeAAAA { |  | ||||||
| 			return m.instanceRecords(q) |  | ||||||
| 		} |  | ||||||
| 		fallthrough |  | ||||||
| 	default: |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (m *MDNSService) serviceEnum(q dns.Question) []dns.RR { |  | ||||||
| 	switch q.Qtype { |  | ||||||
| 	case dns.TypeANY: |  | ||||||
| 		fallthrough |  | ||||||
| 	case dns.TypePTR: |  | ||||||
| 		rr := &dns.PTR{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   q.Name, |  | ||||||
| 				Rrtype: dns.TypePTR, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    atomic.LoadUint32(&m.TTL), |  | ||||||
| 			}, |  | ||||||
| 			Ptr: m.serviceAddr, |  | ||||||
| 		} |  | ||||||
| 		return []dns.RR{rr} |  | ||||||
| 	default: |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // serviceRecords is called when the query matches the service name |  | ||||||
| func (m *MDNSService) serviceRecords(q dns.Question) []dns.RR { |  | ||||||
| 	switch q.Qtype { |  | ||||||
| 	case dns.TypeANY: |  | ||||||
| 		fallthrough |  | ||||||
| 	case dns.TypePTR: |  | ||||||
| 		// Build a PTR response for the service |  | ||||||
| 		rr := &dns.PTR{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   q.Name, |  | ||||||
| 				Rrtype: dns.TypePTR, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    atomic.LoadUint32(&m.TTL), |  | ||||||
| 			}, |  | ||||||
| 			Ptr: m.instanceAddr, |  | ||||||
| 		} |  | ||||||
| 		servRec := []dns.RR{rr} |  | ||||||
|  |  | ||||||
| 		// Get the instance records |  | ||||||
| 		instRecs := m.instanceRecords(dns.Question{ |  | ||||||
| 			Name:  m.instanceAddr, |  | ||||||
| 			Qtype: dns.TypeANY, |  | ||||||
| 		}) |  | ||||||
|  |  | ||||||
| 		// Return the service record with the instance records |  | ||||||
| 		return append(servRec, instRecs...) |  | ||||||
| 	default: |  | ||||||
| 		return nil |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // serviceRecords is called when the query matches the instance name |  | ||||||
| func (m *MDNSService) instanceRecords(q dns.Question) []dns.RR { |  | ||||||
| 	switch q.Qtype { |  | ||||||
| 	case dns.TypeANY: |  | ||||||
| 		// Get the SRV, which includes A and AAAA |  | ||||||
| 		recs := m.instanceRecords(dns.Question{ |  | ||||||
| 			Name:  m.instanceAddr, |  | ||||||
| 			Qtype: dns.TypeSRV, |  | ||||||
| 		}) |  | ||||||
|  |  | ||||||
| 		// Add the TXT record |  | ||||||
| 		recs = append(recs, m.instanceRecords(dns.Question{ |  | ||||||
| 			Name:  m.instanceAddr, |  | ||||||
| 			Qtype: dns.TypeTXT, |  | ||||||
| 		})...) |  | ||||||
| 		return recs |  | ||||||
|  |  | ||||||
| 	case dns.TypeA: |  | ||||||
| 		var rr []dns.RR |  | ||||||
| 		for _, ip := range m.IPs { |  | ||||||
| 			if ip4 := ip.To4(); ip4 != nil { |  | ||||||
| 				rr = append(rr, &dns.A{ |  | ||||||
| 					Hdr: dns.RR_Header{ |  | ||||||
| 						Name:   m.HostName, |  | ||||||
| 						Rrtype: dns.TypeA, |  | ||||||
| 						Class:  dns.ClassINET, |  | ||||||
| 						Ttl:    atomic.LoadUint32(&m.TTL), |  | ||||||
| 					}, |  | ||||||
| 					A: ip4, |  | ||||||
| 				}) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return rr |  | ||||||
|  |  | ||||||
| 	case dns.TypeAAAA: |  | ||||||
| 		var rr []dns.RR |  | ||||||
| 		for _, ip := range m.IPs { |  | ||||||
| 			if ip.To4() != nil { |  | ||||||
| 				// TODO(reddaly): IPv4 addresses could be encoded in IPv6 format and |  | ||||||
| 				// putinto AAAA records, but the current logic puts ipv4-encodable |  | ||||||
| 				// addresses into the A records exclusively.  Perhaps this should be |  | ||||||
| 				// configurable? |  | ||||||
| 				continue |  | ||||||
| 			} |  | ||||||
|  |  | ||||||
| 			if ip16 := ip.To16(); ip16 != nil { |  | ||||||
| 				rr = append(rr, &dns.AAAA{ |  | ||||||
| 					Hdr: dns.RR_Header{ |  | ||||||
| 						Name:   m.HostName, |  | ||||||
| 						Rrtype: dns.TypeAAAA, |  | ||||||
| 						Class:  dns.ClassINET, |  | ||||||
| 						Ttl:    atomic.LoadUint32(&m.TTL), |  | ||||||
| 					}, |  | ||||||
| 					AAAA: ip16, |  | ||||||
| 				}) |  | ||||||
| 			} |  | ||||||
| 		} |  | ||||||
| 		return rr |  | ||||||
|  |  | ||||||
| 	case dns.TypeSRV: |  | ||||||
| 		// Create the SRV Record |  | ||||||
| 		srv := &dns.SRV{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   q.Name, |  | ||||||
| 				Rrtype: dns.TypeSRV, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    atomic.LoadUint32(&m.TTL), |  | ||||||
| 			}, |  | ||||||
| 			Priority: 10, |  | ||||||
| 			Weight:   1, |  | ||||||
| 			Port:     uint16(m.Port), |  | ||||||
| 			Target:   m.HostName, |  | ||||||
| 		} |  | ||||||
| 		recs := []dns.RR{srv} |  | ||||||
|  |  | ||||||
| 		// Add the A record |  | ||||||
| 		recs = append(recs, m.instanceRecords(dns.Question{ |  | ||||||
| 			Name:  m.instanceAddr, |  | ||||||
| 			Qtype: dns.TypeA, |  | ||||||
| 		})...) |  | ||||||
|  |  | ||||||
| 		// Add the AAAA record |  | ||||||
| 		recs = append(recs, m.instanceRecords(dns.Question{ |  | ||||||
| 			Name:  m.instanceAddr, |  | ||||||
| 			Qtype: dns.TypeAAAA, |  | ||||||
| 		})...) |  | ||||||
| 		return recs |  | ||||||
|  |  | ||||||
| 	case dns.TypeTXT: |  | ||||||
| 		txt := &dns.TXT{ |  | ||||||
| 			Hdr: dns.RR_Header{ |  | ||||||
| 				Name:   q.Name, |  | ||||||
| 				Rrtype: dns.TypeTXT, |  | ||||||
| 				Class:  dns.ClassINET, |  | ||||||
| 				Ttl:    atomic.LoadUint32(&m.TTL), |  | ||||||
| 			}, |  | ||||||
| 			Txt: m.TXT, |  | ||||||
| 		} |  | ||||||
| 		return []dns.RR{txt} |  | ||||||
| 	} |  | ||||||
| 	return nil |  | ||||||
| } |  | ||||||
| @@ -1,275 +0,0 @@ | |||||||
| package mdns |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"bytes" |  | ||||||
| 	"net" |  | ||||||
| 	"reflect" |  | ||||||
| 	"testing" |  | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| func makeService(t *testing.T) *MDNSService { |  | ||||||
| 	return makeServiceWithServiceName(t, "_http._tcp") |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func makeServiceWithServiceName(t *testing.T, service string) *MDNSService { |  | ||||||
| 	m, err := NewMDNSService( |  | ||||||
| 		"hostname", |  | ||||||
| 		service, |  | ||||||
| 		"local.", |  | ||||||
| 		"testhost.", |  | ||||||
| 		80, // port |  | ||||||
| 		[]net.IP{net.IP([]byte{192, 168, 0, 42}), net.ParseIP("2620:0:1000:1900:b0c2:d0b2:c411:18bc")}, |  | ||||||
| 		[]string{"Local web server"}) // TXT |  | ||||||
|  |  | ||||||
| 	if err != nil { |  | ||||||
| 		t.Fatalf("err: %v", err) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return m |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestNewMDNSService_BadParams(t *testing.T) { |  | ||||||
| 	for _, test := range []struct { |  | ||||||
| 		testName string |  | ||||||
| 		hostName string |  | ||||||
| 		domain   string |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			"NewMDNSService should fail when passed hostName that is not a legal fully-qualified domain name", |  | ||||||
| 			"hostname", // not legal FQDN - should be "hostname." or "hostname.local.", etc. |  | ||||||
| 			"local.",   // legal |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			"NewMDNSService should fail when passed domain that is not a legal fully-qualified domain name", |  | ||||||
| 			"hostname.", // legal |  | ||||||
| 			"local",     // should be "local." |  | ||||||
| 		}, |  | ||||||
| 	} { |  | ||||||
| 		_, err := NewMDNSService( |  | ||||||
| 			"instance name", |  | ||||||
| 			"_http._tcp", |  | ||||||
| 			test.domain, |  | ||||||
| 			test.hostName, |  | ||||||
| 			80, // port |  | ||||||
| 			[]net.IP{net.IP([]byte{192, 168, 0, 42})}, |  | ||||||
| 			[]string{"Local web server"}) // TXT |  | ||||||
| 		if err == nil { |  | ||||||
| 			t.Fatalf("%s: error expected, but got none", test.testName) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_BadAddr(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "random", |  | ||||||
| 		Qtype: dns.TypeANY, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 0 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_ServiceAddr(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "_http._tcp.local.", |  | ||||||
| 		Qtype: dns.TypeANY, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if got, want := len(recs), 5; got != want { |  | ||||||
| 		t.Fatalf("got %d records, want %d: %v", got, want, recs) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if ptr, ok := recs[0].(*dns.PTR); !ok { |  | ||||||
| 		t.Errorf("recs[0] should be PTR record, got: %v, all records: %v", recs[0], recs) |  | ||||||
| 	} else if got, want := ptr.Ptr, "hostname._http._tcp.local."; got != want { |  | ||||||
| 		t.Fatalf("bad PTR record %v: got %v, want %v", ptr, got, want) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if _, ok := recs[1].(*dns.SRV); !ok { |  | ||||||
| 		t.Errorf("recs[1] should be SRV record, got: %v, all reccords: %v", recs[1], recs) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[2].(*dns.A); !ok { |  | ||||||
| 		t.Errorf("recs[2] should be A record, got: %v, all records: %v", recs[2], recs) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[3].(*dns.AAAA); !ok { |  | ||||||
| 		t.Errorf("recs[3] should be AAAA record, got: %v, all records: %v", recs[3], recs) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[4].(*dns.TXT); !ok { |  | ||||||
| 		t.Errorf("recs[4] should be TXT record, got: %v, all records: %v", recs[4], recs) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	q.Qtype = dns.TypePTR |  | ||||||
| 	if recs2 := s.Records(q); !reflect.DeepEqual(recs, recs2) { |  | ||||||
| 		t.Fatalf("PTR question should return same result as ANY question: ANY => %v, PTR => %v", recs, recs2) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_InstanceAddr_ANY(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "hostname._http._tcp.local.", |  | ||||||
| 		Qtype: dns.TypeANY, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 4 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[0].(*dns.SRV); !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[1].(*dns.A); !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[1]) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[2].(*dns.AAAA); !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[2]) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[3].(*dns.TXT); !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[3]) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_InstanceAddr_SRV(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "hostname._http._tcp.local.", |  | ||||||
| 		Qtype: dns.TypeSRV, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 3 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| 	srv, ok := recs[0].(*dns.SRV) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[1].(*dns.A); !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[1]) |  | ||||||
| 	} |  | ||||||
| 	if _, ok := recs[2].(*dns.AAAA); !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[2]) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	if srv.Port != uint16(s.Port) { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_InstanceAddr_A(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "hostname._http._tcp.local.", |  | ||||||
| 		Qtype: dns.TypeA, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 1 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| 	a, ok := recs[0].(*dns.A) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| 	if !bytes.Equal(a.A, []byte{192, 168, 0, 42}) { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_InstanceAddr_AAAA(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "hostname._http._tcp.local.", |  | ||||||
| 		Qtype: dns.TypeAAAA, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 1 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| 	a4, ok := recs[0].(*dns.AAAA) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| 	ip6 := net.ParseIP("2620:0:1000:1900:b0c2:d0b2:c411:18bc") |  | ||||||
| 	if got := len(ip6); got != net.IPv6len { |  | ||||||
| 		t.Fatalf("test IP failed to parse (len = %d, want %d)", got, net.IPv6len) |  | ||||||
| 	} |  | ||||||
| 	if !bytes.Equal(a4.AAAA, ip6) { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_InstanceAddr_TXT(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "hostname._http._tcp.local.", |  | ||||||
| 		Qtype: dns.TypeTXT, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 1 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| 	txt, ok := recs[0].(*dns.TXT) |  | ||||||
| 	if !ok { |  | ||||||
| 		t.Fatalf("bad: %v", recs[0]) |  | ||||||
| 	} |  | ||||||
| 	if got, want := txt.Txt, s.TXT; !reflect.DeepEqual(got, want) { |  | ||||||
| 		t.Fatalf("TXT record mismatch for %v: got %v, want %v", recs[0], got, want) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_HostNameQuery(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	for _, test := range []struct { |  | ||||||
| 		q    dns.Question |  | ||||||
| 		want []dns.RR |  | ||||||
| 	}{ |  | ||||||
| 		{ |  | ||||||
| 			dns.Question{Name: "testhost.", Qtype: dns.TypeA}, |  | ||||||
| 			[]dns.RR{&dns.A{ |  | ||||||
| 				Hdr: dns.RR_Header{ |  | ||||||
| 					Name:   "testhost.", |  | ||||||
| 					Rrtype: dns.TypeA, |  | ||||||
| 					Class:  dns.ClassINET, |  | ||||||
| 					Ttl:    120, |  | ||||||
| 				}, |  | ||||||
| 				A: net.IP([]byte{192, 168, 0, 42}), |  | ||||||
| 			}}, |  | ||||||
| 		}, |  | ||||||
| 		{ |  | ||||||
| 			dns.Question{Name: "testhost.", Qtype: dns.TypeAAAA}, |  | ||||||
| 			[]dns.RR{&dns.AAAA{ |  | ||||||
| 				Hdr: dns.RR_Header{ |  | ||||||
| 					Name:   "testhost.", |  | ||||||
| 					Rrtype: dns.TypeAAAA, |  | ||||||
| 					Class:  dns.ClassINET, |  | ||||||
| 					Ttl:    120, |  | ||||||
| 				}, |  | ||||||
| 				AAAA: net.ParseIP("2620:0:1000:1900:b0c2:d0b2:c411:18bc"), |  | ||||||
| 			}}, |  | ||||||
| 		}, |  | ||||||
| 	} { |  | ||||||
| 		if got := s.Records(test.q); !reflect.DeepEqual(got, test.want) { |  | ||||||
| 			t.Errorf("hostname query failed: s.Records(%v) = %v, want %v", test.q, got, test.want) |  | ||||||
| 		} |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func TestMDNSService_serviceEnum_PTR(t *testing.T) { |  | ||||||
| 	s := makeService(t) |  | ||||||
| 	q := dns.Question{ |  | ||||||
| 		Name:  "_services._dns-sd._udp.local.", |  | ||||||
| 		Qtype: dns.TypePTR, |  | ||||||
| 	} |  | ||||||
| 	recs := s.Records(q) |  | ||||||
| 	if len(recs) != 1 { |  | ||||||
| 		t.Fatalf("bad: %v", recs) |  | ||||||
| 	} |  | ||||||
| 	if ptr, ok := recs[0].(*dns.PTR); !ok { |  | ||||||
| 		t.Errorf("recs[0] should be PTR record, got: %v, all records: %v", recs[0], recs) |  | ||||||
| 	} else if got, want := ptr.Ptr, "_http._tcp.local."; got != want { |  | ||||||
| 		t.Fatalf("bad PTR record %v: got %v, want %v", ptr, got, want) |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -85,12 +85,12 @@ func CSR(opts ...CertOption) ([]byte, error) { | |||||||
| } | } | ||||||
|  |  | ||||||
| // Sign decodes a CSR and signs it with the CA | // Sign decodes a CSR and signs it with the CA | ||||||
| func Sign(CACrt, CAKey, CSR []byte, opts ...CertOption) ([]byte, error) { | func Sign(crt, key, csr []byte, opts ...CertOption) ([]byte, error) { | ||||||
| 	options := CertOptions{} | 	options := CertOptions{} | ||||||
| 	for _, o := range opts { | 	for _, o := range opts { | ||||||
| 		o(&options) | 		o(&options) | ||||||
| 	} | 	} | ||||||
| 	asn1CACrt, err := decodePEM(CACrt) | 	asn1CACrt, err := decodePEM(crt) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to decode CA Crt PEM: %w", err) | 		return nil, fmt.Errorf("failed to decode CA Crt PEM: %w", err) | ||||||
| 	} | 	} | ||||||
| @@ -101,7 +101,7 @@ func Sign(CACrt, CAKey, CSR []byte, opts ...CertOption) ([]byte, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("ca is not a valid certificate: %w", err) | 		return nil, fmt.Errorf("ca is not a valid certificate: %w", err) | ||||||
| 	} | 	} | ||||||
| 	asn1CAKey, err := decodePEM(CAKey) | 	asn1CAKey, err := decodePEM(key) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to decode CA  Key PEM: %w", err) | 		return nil, fmt.Errorf("failed to decode CA  Key PEM: %w", err) | ||||||
| 	} | 	} | ||||||
| @@ -112,22 +112,22 @@ func Sign(CACrt, CAKey, CSR []byte, opts ...CertOption) ([]byte, error) { | |||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("ca key is not a valid private key: %w", err) | 		return nil, fmt.Errorf("ca key is not a valid private key: %w", err) | ||||||
| 	} | 	} | ||||||
| 	asn1CSR, err := decodePEM(CSR) | 	asn1CSR, err := decodePEM(csr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("failed to decode CSR PEM: %w", err) | 		return nil, fmt.Errorf("failed to decode CSR PEM: %w", err) | ||||||
| 	} | 	} | ||||||
| 	if len(asn1CSR) != 1 { | 	if len(asn1CSR) != 1 { | ||||||
| 		return nil, fmt.Errorf("expected 1 CSR, got %d", len(asn1CSR)) | 		return nil, fmt.Errorf("expected 1 CSR, got %d", len(asn1CSR)) | ||||||
| 	} | 	} | ||||||
| 	csr, err := x509.ParseCertificateRequest(asn1CSR[0].Bytes) | 	caCsr, err := x509.ParseCertificateRequest(asn1CSR[0].Bytes) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return nil, fmt.Errorf("csr is invalid: %w", err) | 		return nil, fmt.Errorf("csr is invalid: %w", err) | ||||||
| 	} | 	} | ||||||
| 	template := &x509.Certificate{ | 	template := &x509.Certificate{ | ||||||
| 		SignatureAlgorithm:    x509.PureEd25519, | 		SignatureAlgorithm:    x509.PureEd25519, | ||||||
| 		Subject:               csr.Subject, | 		Subject:               caCsr.Subject, | ||||||
| 		DNSNames:              csr.DNSNames, | 		DNSNames:              caCsr.DNSNames, | ||||||
| 		IPAddresses:           csr.IPAddresses, | 		IPAddresses:           caCsr.IPAddresses, | ||||||
| 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | 		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, | ||||||
| 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | 		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, | ||||||
| 		NotBefore:             options.NotBefore, | 		NotBefore:             options.NotBefore, | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| package pki | package pki | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
| 	"crypto/ed25519" |  | ||||||
| 	"crypto/rand" | 	"crypto/rand" | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"crypto/x509/pkix" | 	"crypto/x509/pkix" | ||||||
| @@ -10,22 +9,26 @@ import ( | |||||||
| 	"net" | 	"net" | ||||||
| 	"testing" | 	"testing" | ||||||
| 	"time" | 	"time" | ||||||
|  |  | ||||||
| 	"github.com/stretchr/testify/assert" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func TestPrivateKey(t *testing.T) { | func TestPrivateKey(t *testing.T) { | ||||||
| 	_, _, err := GenerateKey() | 	_, _, err := GenerateKey() | ||||||
| 	assert.NoError(t, err) | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestCA(t *testing.T) { | func TestCA(t *testing.T) { | ||||||
| 	pub, priv, err := GenerateKey() | 	pub, priv, err := GenerateKey() | ||||||
| 	assert.NoError(t, err) | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	serialNumberMax := new(big.Int).Lsh(big.NewInt(1), 128) | 	serialNumberMax := new(big.Int).Lsh(big.NewInt(1), 128) | ||||||
| 	serialNumber, err := rand.Int(rand.Reader, serialNumberMax) | 	serialNumber, err := rand.Int(rand.Reader, serialNumberMax) | ||||||
| 	assert.NoError(t, err, "Couldn't generate serial") | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	cert, key, err := CA( | 	cert, key, err := CA( | ||||||
| 		KeyPair(pub, priv), | 		KeyPair(pub, priv), | ||||||
| @@ -38,31 +41,57 @@ func TestCA(t *testing.T) { | |||||||
| 		NotBefore(time.Now().Add(time.Minute*-1)), | 		NotBefore(time.Now().Add(time.Minute*-1)), | ||||||
| 		NotAfter(time.Now().Add(time.Minute)), | 		NotAfter(time.Now().Add(time.Minute)), | ||||||
| 	) | 	) | ||||||
| 	assert.NoError(t, err, "Couldn't sign CA") | 	if err != nil { | ||||||
| 	asn1Key, _ := pem.Decode(key) | 		t.Fatal(err) | ||||||
| 	assert.NotNil(t, asn1Key, "Couldn't decode key") | 	} | ||||||
| 	assert.Equal(t, "PRIVATE KEY", asn1Key.Type) |  | ||||||
| 	decodedKey, err := x509.ParsePKCS8PrivateKey(asn1Key.Bytes) |  | ||||||
| 	assert.NoError(t, err, "Couldn't decode ASN1 Key") |  | ||||||
| 	assert.Equal(t, priv, decodedKey.(ed25519.PrivateKey)) |  | ||||||
|  |  | ||||||
| 	pool := x509.NewCertPool() | 	asn1Key, _ := pem.Decode(key) | ||||||
| 	assert.True(t, pool.AppendCertsFromPEM(cert), "Coudn't parse cert") | 	if asn1Key == nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  | 	if asn1Key.Type != "PRIVATE KEY" { | ||||||
|  | 		t.Fatal("invalid key type") | ||||||
|  | 	} | ||||||
|  | 	decodedKey, err := x509.ParsePKCS8PrivateKey(asn1Key.Bytes) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} else if decodedKey == nil { | ||||||
|  | 		t.Fatal("empty key") | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	asn1Cert, _ := pem.Decode(cert) | 	asn1Cert, _ := pem.Decode(cert) | ||||||
| 	assert.NotNil(t, asn1Cert, "Couldn't parse pem cert") | 	if asn1Cert == nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 		pool := x509.NewCertPool() | ||||||
|  |  | ||||||
| 		x509cert, err := x509.ParseCertificate(asn1Cert.Bytes) | 		x509cert, err := x509.ParseCertificate(asn1Cert.Bytes) | ||||||
| 	assert.NoError(t, err, "Couldn't parse asn1 cert") | 		if err != nil { | ||||||
|  | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  |  | ||||||
| 		chains, err := x509cert.Verify(x509.VerifyOptions{ | 		chains, err := x509cert.Verify(x509.VerifyOptions{ | ||||||
| 			Roots: pool, | 			Roots: pool, | ||||||
| 		}) | 		}) | ||||||
| 	assert.NoError(t, err, "Cert didn't verify") | 		if err != nil { | ||||||
| 	assert.Len(t, chains, 1, "CA should have 1 cert in chain") | 			t.Fatal(err) | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if len(chains) != 1 { | ||||||
|  | 			t.Fatal("CA should have 1 cert in chain") | ||||||
|  | 		} | ||||||
|  | 	*/ | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestCSR(t *testing.T) { | func TestCSR(t *testing.T) { | ||||||
| 	pub, priv, err := GenerateKey() | 	pub, priv, err := GenerateKey() | ||||||
| 	assert.NoError(t, err) | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	csr, err := CSR( | 	csr, err := CSR( | ||||||
| 		Subject( | 		Subject( | ||||||
| 			pkix.Name{ | 			pkix.Name{ | ||||||
| @@ -75,16 +104,26 @@ func TestCSR(t *testing.T) { | |||||||
| 		IPAddresses(net.ParseIP("127.0.0.1")), | 		IPAddresses(net.ParseIP("127.0.0.1")), | ||||||
| 		KeyPair(pub, priv), | 		KeyPair(pub, priv), | ||||||
| 	) | 	) | ||||||
| 	assert.NoError(t, err, "CSR couldn't be encoded") | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	asn1csr, _ := pem.Decode(csr) | 	asn1csr, _ := pem.Decode(csr) | ||||||
| 	assert.NotNil(t, asn1csr) | 	if asn1csr == nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	decodedcsr, err := x509.ParseCertificateRequest(asn1csr.Bytes) | 	decodedcsr, err := x509.ParseCertificateRequest(asn1csr.Bytes) | ||||||
| 	assert.NoError(t, err) | 	if err != nil { | ||||||
|  | 		t.Fatal(err) | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	expected := pkix.Name{ | 	expected := pkix.Name{ | ||||||
| 		CommonName:         "testnode", | 		CommonName:         "testnode", | ||||||
| 		Organization:       []string{"microtest"}, | 		Organization:       []string{"microtest"}, | ||||||
| 		OrganizationalUnit: []string{"super-testers"}, | 		OrganizationalUnit: []string{"super-testers"}, | ||||||
| 	} | 	} | ||||||
| 	assert.Equal(t, decodedcsr.Subject.String(), expected.String()) | 	if decodedcsr.Subject.String() != expected.String() { | ||||||
|  | 		t.Fatalf("%s != %s", decodedcsr.Subject.String(), expected.String()) | ||||||
|  | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,52 +0,0 @@ | |||||||
| package scope |  | ||||||
|  |  | ||||||
| import ( |  | ||||||
| 	"context" |  | ||||||
| 	"fmt" |  | ||||||
|  |  | ||||||
| 	"github.com/unistack-org/micro/v3/store" |  | ||||||
| ) |  | ||||||
|  |  | ||||||
| // Scope extends the store, applying a prefix to each request |  | ||||||
| type Scope struct { |  | ||||||
| 	store.Store |  | ||||||
| 	prefix string |  | ||||||
| } |  | ||||||
|  |  | ||||||
| // NewScope returns an initialised scope |  | ||||||
| func NewScope(s store.Store, prefix string) Scope { |  | ||||||
| 	return Scope{Store: s, prefix: prefix} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Scope) Options() store.Options { |  | ||||||
| 	o := s.Store.Options() |  | ||||||
| 	o.Table = s.prefix |  | ||||||
| 	return o |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Scope) Read(ctx context.Context, key string, val interface{}, opts ...store.ReadOption) error { |  | ||||||
| 	key = fmt.Sprintf("%v/%v", s.prefix, key) |  | ||||||
| 	return s.Store.Read(ctx, key, val, opts...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Scope) Write(ctx context.Context, key string, val interface{}, opts ...store.WriteOption) error { |  | ||||||
| 	key = fmt.Sprintf("%v/%v", s.prefix, key) |  | ||||||
| 	return s.Store.Write(ctx, key, val, opts...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Scope) Delete(ctx context.Context, key string, opts ...store.DeleteOption) error { |  | ||||||
| 	key = fmt.Sprintf("%v/%v", s.prefix, key) |  | ||||||
| 	return s.Store.Delete(ctx, key, opts...) |  | ||||||
| } |  | ||||||
|  |  | ||||||
| func (s *Scope) List(ctx context.Context, opts ...store.ListOption) ([]string, error) { |  | ||||||
| 	var lops store.ListOptions |  | ||||||
| 	for _, o := range opts { |  | ||||||
| 		o(&lops) |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	key := fmt.Sprintf("%v/%v", s.prefix, lops.Prefix) |  | ||||||
| 	opts = append(opts, store.ListPrefix(key)) |  | ||||||
|  |  | ||||||
| 	return s.Store.List(ctx, opts...) |  | ||||||
| } |  | ||||||
		Reference in New Issue
	
	Block a user