diff --git a/api/handler/api/api.go b/api/handler/api/api.go deleted file mode 100644 index 377af367..00000000 --- a/api/handler/api/api.go +++ /dev/null @@ -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, - } -} diff --git a/api/handler/api/util.go b/api/handler/api/util.go deleted file mode 100644 index cb541f9e..00000000 --- a/api/handler/api/util.go +++ /dev/null @@ -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 -} diff --git a/api/handler/api/util_test.go b/api/handler/api/util_test.go deleted file mode 100644 index 9ac69068..00000000 --- a/api/handler/api/util_test.go +++ /dev/null @@ -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]) - } - } - } -} diff --git a/api/handler/event/event.go b/api/handler/event/event.go deleted file mode 100644 index cc0df122..00000000 --- a/api/handler/event/event.go +++ /dev/null @@ -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...), - } -} diff --git a/api/handler/http/http.go b/api/handler/http/http.go deleted file mode 100644 index 4ab499f3..00000000 --- a/api/handler/http/http.go +++ /dev/null @@ -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, - } -} diff --git a/api/handler/http/http_test.go b/api/handler/http/http_test.go deleted file mode 100644 index 65b4a17d..00000000 --- a/api/handler/http/http_test.go +++ /dev/null @@ -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) - }) - } -} diff --git a/api/handler/options.go b/api/handler/options.go index 59607280..02d6d0d6 100644 --- a/api/handler/options.go +++ b/api/handler/options.go @@ -3,6 +3,7 @@ package handler import ( "github.com/unistack-org/micro/v3/api/router" "github.com/unistack-org/micro/v3/client" + "github.com/unistack-org/micro/v3/logger" ) var ( @@ -14,13 +15,19 @@ type Options struct { Namespace string Router router.Router Client client.Client + Logger logger.Logger } type Option func(o *Options) // NewOptions fills in the blanks 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 { o(&options) } @@ -30,10 +37,6 @@ func NewOptions(opts ...Option) Options { WithNamespace("go.micro.api")(&options) } - if options.MaxRecvSize == 0 { - options.MaxRecvSize = DefaultMaxRecvSize - } - return options } diff --git a/api/handler/web/web.go b/api/handler/web/web.go deleted file mode 100644 index 47cd47bc..00000000 --- a/api/handler/web/web.go +++ /dev/null @@ -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, - } -} diff --git a/api/internal/proto/message.pb.go b/api/internal/proto/message.pb.go deleted file mode 100644 index aae2743f..00000000 --- a/api/internal/proto/message.pb.go +++ /dev/null @@ -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} -} diff --git a/api/proto/api.pb.go b/api/proto/api.pb.go deleted file mode 100644 index f2a109e4..00000000 --- a/api/proto/api.pb.go +++ /dev/null @@ -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 -} diff --git a/api/proto/api.pb.micro.go b/api/proto/api.pb.micro.go deleted file mode 100644 index 9b91e09d..00000000 --- a/api/proto/api.pb.micro.go +++ /dev/null @@ -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 diff --git a/api/proto/api.proto b/api/proto/api.proto deleted file mode 100644 index fd0f9fec..00000000 --- a/api/proto/api.proto +++ /dev/null @@ -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 header = 3; - map get = 4; - map 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 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 header = 4; - // the event data - string data = 5; -} diff --git a/api/resolver/subdomain/subdomain_test.go b/api/resolver/subdomain/subdomain_test.go index 79fadd30..cb817efd 100644 --- a/api/resolver/subdomain/subdomain_test.go +++ b/api/resolver/subdomain/subdomain_test.go @@ -6,8 +6,6 @@ import ( "testing" "github.com/unistack-org/micro/v3/api/resolver/vpath" - - "github.com/stretchr/testify/assert" ) func TestResolve(t *testing.T) { @@ -62,9 +60,13 @@ func TestResolve(t *testing.T) { t.Run(tc.Name, func(t *testing.T) { r := NewResolver(vpath.NewResolver()) 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 { - 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) + } } }) } diff --git a/api/router/router.go b/api/router/router.go index 923737a6..fd92fcf4 100644 --- a/api/router/router.go +++ b/api/router/router.go @@ -7,6 +7,10 @@ import ( "github.com/unistack-org/micro/v3/api" ) +var ( + DefaultRouter Router +) + // Router is used to determine an endpoint for a request type Router interface { // Returns options @@ -23,6 +27,6 @@ type Router interface { Deregister(ep *api.Endpoint) error // Route returns an api.Service route Route(r *http.Request) (*api.Service, error) - // String represenation of router + // String representation of router String() string } diff --git a/broker/memory.go b/broker/memory.go index 0a19169b..57a644a3 100644 --- a/broker/memory.go +++ b/broker/memory.go @@ -235,6 +235,7 @@ func (m *memorySubscriber) Unsubscribe(ctx context.Context) error { return nil } +// NewBroker return new memory broker func NewBroker(opts ...Option) Broker { rand.Seed(time.Now().UnixNano()) diff --git a/client/client.go b/client/client.go index e3ffc87c..d577b55f 100644 --- a/client/client.go +++ b/client/client.go @@ -11,7 +11,8 @@ import ( var ( // 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. diff --git a/client/noop.go b/client/noop.go index 2d285f8d..a0c6028b 100644 --- a/client/noop.go +++ b/client/noop.go @@ -20,10 +20,6 @@ var ( } ) -const ( - defaultContentType = "application/json" -) - type noopClient struct { opts Options } diff --git a/client/options.go b/client/options.go index 9ff18ee7..767d15e0 100644 --- a/client/options.go +++ b/client/options.go @@ -151,7 +151,7 @@ type RequestOptions struct { func NewOptions(opts ...Option) Options { options := Options{ Context: context.Background(), - ContentType: "application/json", + ContentType: DefaultContentType, Codecs: make(map[string]codec.Codec), CallOptions: CallOptions{ Backoff: DefaultBackoff, diff --git a/codec/codec.go b/codec/codec.go index 5702b8fb..a7c8922c 100644 --- a/codec/codec.go +++ b/codec/codec.go @@ -8,8 +8,8 @@ import ( "github.com/unistack-org/micro/v3/metadata" ) +// Message types const ( - // Message types Error MessageType = iota Request Response @@ -25,8 +25,9 @@ var ( var ( // DefaultMaxMsgSize specifies how much data codec can handle - DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb - DefaultCodec Codec = NewCodec() + DefaultMaxMsgSize int = 1024 * 1024 * 4 // 4Mb + // DefaultCodec is the global default codec + DefaultCodec Codec = NewCodec() ) // MessageType diff --git a/codec/noop.go b/codec/noop.go index 38dbc754..ad3564e6 100644 --- a/codec/noop.go +++ b/codec/noop.go @@ -30,12 +30,8 @@ func (c *noopCodec) ReadBody(conn io.Reader, b interface{}) error { } switch v := b.(type) { - case string: - v = string(buf) case *string: *v = string(buf) - case []byte: - v = buf case *[]byte: *v = buf case *Frame: @@ -112,15 +108,9 @@ func (c *noopCodec) Unmarshal(d []byte, v interface{}) error { return nil } switch ve := v.(type) { - case string: - ve = string(d) - return nil case *string: *ve = string(d) return nil - case []byte: - ve = d - return nil case *[]byte: *ve = d return nil diff --git a/codec/noop_test.go b/codec/noop_test.go new file mode 100644 index 00000000..9f99b5dd --- /dev/null +++ b/codec/noop_test.go @@ -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) + } +} diff --git a/codec/options.go b/codec/options.go index 8cbb0537..4cb39323 100644 --- a/codec/options.go +++ b/codec/options.go @@ -45,6 +45,7 @@ func Meter(m meter.Meter) Option { } } +// NewOptions returns new options func NewOptions(opts ...Option) Options { options := Options{ Logger: logger.DefaultLogger, diff --git a/config/context.go b/config/context.go index 04e262f0..7bc54872 100644 --- a/config/context.go +++ b/config/context.go @@ -6,6 +6,7 @@ import ( type configKey struct{} +// FromContext returns store from context func FromContext(ctx context.Context) (Config, bool) { if ctx == nil { return nil, false @@ -14,6 +15,7 @@ func FromContext(ctx context.Context) (Config, bool) { return c, ok } +// NewContext put store in context func NewContext(ctx context.Context, c Config) context.Context { if ctx == nil { ctx = context.Background() diff --git a/debug/log/kubernetes/kubernetes.go b/debug/log/kubernetes/kubernetes.go deleted file mode 100644 index 9fc17b10..00000000 --- a/debug/log/kubernetes/kubernetes.go +++ /dev/null @@ -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 -} diff --git a/debug/log/kubernetes/kubernetes_test.go b/debug/log/kubernetes/kubernetes_test.go deleted file mode 100644 index 178de391..00000000 --- a/debug/log/kubernetes/kubernetes_test.go +++ /dev/null @@ -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") -} diff --git a/debug/log/kubernetes/stream.go b/debug/log/kubernetes/stream.go deleted file mode 100644 index 957964ca..00000000 --- a/debug/log/kubernetes/stream.go +++ /dev/null @@ -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 -} diff --git a/function.go b/function.go index e8f4f2b5..391d7ede 100644 --- a/function.go +++ b/function.go @@ -64,7 +64,7 @@ func newFunction(opts ...Option) Function { // make context the last thing fopts = append(fopts, Context(ctx)) - service := &service{opts: NewOptions(opts...)} + service := &service{opts: NewOptions(fopts...)} fn := &function{ cancel: cancel, diff --git a/go.mod b/go.mod index caa0b3f7..caf2ccc2 100644 --- a/go.mod +++ b/go.mod @@ -6,18 +6,12 @@ require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/ef-ds/deque v1.0.4 - github.com/golang/protobuf v1.4.3 github.com/google/uuid v1.2.0 github.com/imdario/mergo v0.3.11 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/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c github.com/patrickmn/go-cache v2.1.0+incompatible 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/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect - google.golang.org/protobuf v1.25.0 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 354ca6e2..f1a8448c 100644 --- a/go.sum +++ b/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/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= @@ -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/ef-ds/deque v1.0.4 h1:iFAZNmveMT9WERAkqLJ+oaABF9AcVQ5AjXem/hroniI= 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/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 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.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 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/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/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/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/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/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/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/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/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/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-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 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= 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.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 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= diff --git a/meter/meter_test.go b/meter/meter_test.go index 9281adcd..00bd0394 100644 --- a/meter/meter_test.go +++ b/meter/meter_test.go @@ -2,19 +2,16 @@ package meter import ( "testing" - - "github.com/stretchr/testify/assert" ) func TestNoopMeter(t *testing.T) { meter := NewMeter(Path("/noop")) - assert.NotNil(t, meter) - assert.Equal(t, "/noop", meter.Options().Path) - assert.Implements(t, new(Meter), meter) + if "/noop" != meter.Options().Path { + t.Fatalf("invalid options parsing: %v", meter.Options()) + } cnt := meter.Counter("counter", Label("server", "noop")) cnt.Inc() - } func TestLabels(t *testing.T) { diff --git a/resolver/dns/dns.go b/resolver/dns/dns.go index 4c3ddd01..0fa2be90 100644 --- a/resolver/dns/dns.go +++ b/resolver/dns/dns.go @@ -4,18 +4,20 @@ package dns import ( "context" "net" + "sync" + "time" - "github.com/miekg/dns" "github.com/unistack-org/micro/v3/resolver" ) // Resolver is a DNS network resolve type Resolver struct { // 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) { host, port, err := net.SplitHostPort(name) if err != nil { @@ -28,56 +30,46 @@ func (r *Resolver) Resolve(name string) ([]*resolver.Record, error) { } 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 if v := net.ParseIP(host); v != nil { - records = append(records, &resolver.Record{ - Address: net.JoinHostPort(host, port), - }) - return records, nil + rec := &resolver.Record{Address: net.JoinHostPort(host, port)} + return []*resolver.Record{rec}, nil } - for _, q := range []uint16{dns.TypeA, dns.TypeAAAA} { - m := new(dns.Msg) - m.SetQuestion(dns.Fqdn(host), q) - rec, err := dns.ExchangeContext(context.Background(), m, r.Address) - if err != nil { - return nil, err - } + r.RLock() + goresolver := r.goresolver + r.RUnlock() - var addr string - for _, answer := range rec.Answer { - h := answer.Header() - // 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 - address := net.JoinHostPort(addr, port) - // append to record set - records = append(records, &resolver.Record{ - Address: 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() } - // no records returned so just best effort it - if len(records) == 0 { + addrs, err := goresolver.LookupIP(context.TODO(), "ip", host) + if err != nil { + return nil, err + } + + if len(addrs) == 0 { + rec := &resolver.Record{Address: net.JoinHostPort(host, port)} + return []*resolver.Record{rec}, nil + } + + records := make([]*resolver.Record, 0, len(addrs)) + for _, addr := range addrs { records = append(records, &resolver.Record{ - Address: net.JoinHostPort(host, port), + Address: net.JoinHostPort(addr.String(), port), }) } diff --git a/resolver/dns/dns_test.go b/resolver/dns/dns_test.go new file mode 100644 index 00000000..bae9a6a6 --- /dev/null +++ b/resolver/dns/dns_test.go @@ -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) + } +} diff --git a/selector/roundrobin/roundrobin_test.go b/selector/roundrobin/roundrobin_test.go index 0ceb082e..e23e45da 100644 --- a/selector/roundrobin/roundrobin_test.go +++ b/selector/roundrobin/roundrobin_test.go @@ -3,7 +3,6 @@ package roundrobin import ( "testing" - "github.com/stretchr/testify/assert" "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) next, err := sel.Select([]string{r1}) + if err != nil { + t.Fatal(err) + } 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}) + if err != nil { + t.Fatal(err) + } r = next() - assert.Nil(t, err, "Error should be nil") - assert.Equal(t, r2, r, "Expected route to be r2") + if r2 != r { + t.Fatal("Expected route to be r2") + } routes := []string{r1, r2, r3} 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() // start element is random but then it should loop through in order @@ -41,9 +51,19 @@ func TestRoundRobin(t *testing.T) { break } } - assert.NotEqual(t, start, -1) - assert.Equal(t, routes[start], n1, "Unexpected route") - assert.Equal(t, routes[(start+1)%3], n2, "Unexpected route") - assert.Equal(t, routes[(start+2)%3], n3, "Unexpected route") - assert.Equal(t, routes[(start+3)%3], n4, "Unexpected route") + if start == -1 { + t.Fatalf("start == -1 %v %v", start, -1) + } + if routes[start] != n1 { + 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") + } } diff --git a/selector/tests.go b/selector/tests.go index 6829b0c9..2bb07855 100644 --- a/selector/tests.go +++ b/selector/tests.go @@ -2,8 +2,6 @@ package selector import ( "testing" - - "github.com/stretchr/testify/assert" ) // 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("NoRoutes", func(t *testing.T) { _, 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) { next, err := s.Select([]string{r1}) + if err != nil { + t.Fatal("Error should be nil") + } srv := next() - assert.Nil(t, err, "Error should be nil") - assert.Equal(t, r1, srv, "Expected the route to be returned") + if r1 != srv { + t.Fatal("Expected the route to be returned") + } }) t.Run("MultipleRoutes", func(t *testing.T) { 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() if srv != r1 && srv != r2 { 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) { - err := s.Record(r1, nil) - assert.Nil(t, err, "Expected the error to be nil") + if err := s.Record(r1, nil); err != nil { + t.Fatal("Expected the error to be nil") + } }) 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") + } }) } diff --git a/store/memory_test.go b/store/memory_test.go index b46a9867..d3e82e25 100644 --- a/store/memory_test.go +++ b/store/memory_test.go @@ -2,7 +2,6 @@ package store_test import ( "context" - "os" "testing" "time" @@ -53,9 +52,6 @@ func TestMemoryNamespacePrefix(t *testing.T) { func basictest(s store.Store, t *testing.T) { 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 if err := s.Write(ctx, "Hello", "World", store.WriteTTL(time.Millisecond*100)); err != nil { t.Error(err) @@ -71,5 +67,7 @@ func basictest(s store.Store, t *testing.T) { 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) + } } diff --git a/util/http/http.go b/util/http/http.go index 918eea51..d2d353d2 100644 --- a/util/http/http.go +++ b/util/http/http.go @@ -60,10 +60,9 @@ func NewRoundTripper(opts ...Option) http.RoundTripper { // RequestToContext puts the `Authorization` header bearer token into context // so calls to services will be authorized. func RequestToContext(r *http.Request) context.Context { - ctx := context.Background() - md := make(metadata.Metadata) + md := metadata.New(len(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) } diff --git a/util/kubernetes/api/api_test.go b/util/kubernetes/api/api_test.go deleted file mode 100644 index 26474b17..00000000 --- a/util/kubernetes/api/api_test.go +++ /dev/null @@ -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() - } -} diff --git a/util/kubernetes/api/request.go b/util/kubernetes/api/request.go deleted file mode 100644 index cc11b438..00000000 --- a/util/kubernetes/api/request.go +++ /dev/null @@ -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 -} diff --git a/util/kubernetes/api/response.go b/util/kubernetes/api/response.go deleted file mode 100644 index 14de9f28..00000000 --- a/util/kubernetes/api/response.go +++ /dev/null @@ -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 -} diff --git a/util/kubernetes/client/client.go b/util/kubernetes/client/client.go deleted file mode 100644 index 2635db93..00000000 --- a/util/kubernetes/client/client.go +++ /dev/null @@ -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, - }, - } -} diff --git a/util/kubernetes/client/options.go b/util/kubernetes/client/options.go deleted file mode 100644 index dd6b32a5..00000000 --- a/util/kubernetes/client/options.go +++ /dev/null @@ -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) - } -} diff --git a/util/kubernetes/client/templates.go b/util/kubernetes/client/templates.go deleted file mode 100644 index ee35b483..00000000 --- a/util/kubernetes/client/templates.go +++ /dev/null @@ -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 }} -` diff --git a/util/kubernetes/client/types.go b/util/kubernetes/client/types.go deleted file mode 100644 index da78d1dd..00000000 --- a/util/kubernetes/client/types.go +++ /dev/null @@ -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"` -} diff --git a/util/kubernetes/client/util.go b/util/kubernetes/client/util.go deleted file mode 100644 index 747ac9cb..00000000 --- a/util/kubernetes/client/util.go +++ /dev/null @@ -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 -} diff --git a/util/kubernetes/client/util_test.go b/util/kubernetes/client/util_test.go deleted file mode 100644 index 54a6139c..00000000 --- a/util/kubernetes/client/util_test.go +++ /dev/null @@ -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) - } - } -} diff --git a/util/kubernetes/client/watch.go b/util/kubernetes/client/watch.go deleted file mode 100644 index dc799c43..00000000 --- a/util/kubernetes/client/watch.go +++ /dev/null @@ -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 -} diff --git a/util/mdns/.gitignore b/util/mdns/.gitignore deleted file mode 100644 index 83656241..00000000 --- a/util/mdns/.gitignore +++ /dev/null @@ -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 diff --git a/util/mdns/client.go b/util/mdns/client.go deleted file mode 100644 index 78c2cb70..00000000 --- a/util/mdns/client.go +++ /dev/null @@ -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 -} diff --git a/util/mdns/dns_sd.go b/util/mdns/dns_sd.go deleted file mode 100644 index 18444c34..00000000 --- a/util/mdns/dns_sd.go +++ /dev/null @@ -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.", 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." 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." yields a set of - // PTR records, where the rdata of each PTR record is the two-abel - // name, plus the same domain, e.g., "_http._tcp.". - // 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 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() -//} diff --git a/util/mdns/dns_sd_test.go b/util/mdns/dns_sd_test.go deleted file mode 100644 index 8b4c11ce..00000000 --- a/util/mdns/dns_sd_test.go +++ /dev/null @@ -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) - } -} diff --git a/util/mdns/server.go b/util/mdns/server.go deleted file mode 100644 index d4b532f8..00000000 --- a/util/mdns/server.go +++ /dev/null @@ -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 -} diff --git a/util/mdns/server_test.go b/util/mdns/server_test.go deleted file mode 100644 index b1488c16..00000000 --- a/util/mdns/server_test.go +++ /dev/null @@ -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") - } -} diff --git a/util/mdns/zone.go b/util/mdns/zone.go deleted file mode 100644 index abbab4bd..00000000 --- a/util/mdns/zone.go +++ /dev/null @@ -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. -} - -// 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 -} diff --git a/util/mdns/zone_test.go b/util/mdns/zone_test.go deleted file mode 100644 index 082d72dd..00000000 --- a/util/mdns/zone_test.go +++ /dev/null @@ -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) - } -} diff --git a/util/pki/pki.go b/util/pki/pki.go index 1d2f3a85..1676549a 100644 --- a/util/pki/pki.go +++ b/util/pki/pki.go @@ -85,12 +85,12 @@ func CSR(opts ...CertOption) ([]byte, error) { } // 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{} for _, o := range opts { o(&options) } - asn1CACrt, err := decodePEM(CACrt) + asn1CACrt, err := decodePEM(crt) if err != nil { 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 { return nil, fmt.Errorf("ca is not a valid certificate: %w", err) } - asn1CAKey, err := decodePEM(CAKey) + asn1CAKey, err := decodePEM(key) if err != nil { 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 { 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 { return nil, fmt.Errorf("failed to decode CSR PEM: %w", err) } if len(asn1CSR) != 1 { 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 { return nil, fmt.Errorf("csr is invalid: %w", err) } template := &x509.Certificate{ SignatureAlgorithm: x509.PureEd25519, - Subject: csr.Subject, - DNSNames: csr.DNSNames, - IPAddresses: csr.IPAddresses, + Subject: caCsr.Subject, + DNSNames: caCsr.DNSNames, + IPAddresses: caCsr.IPAddresses, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, NotBefore: options.NotBefore, diff --git a/util/pki/pki_test.go b/util/pki/pki_test.go index 67e81d13..dadaf300 100644 --- a/util/pki/pki_test.go +++ b/util/pki/pki_test.go @@ -1,7 +1,6 @@ package pki import ( - "crypto/ed25519" "crypto/rand" "crypto/x509" "crypto/x509/pkix" @@ -10,22 +9,26 @@ import ( "net" "testing" "time" - - "github.com/stretchr/testify/assert" ) func TestPrivateKey(t *testing.T) { _, _, err := GenerateKey() - assert.NoError(t, err) + if err != nil { + t.Fatal(err) + } } func TestCA(t *testing.T) { pub, priv, err := GenerateKey() - assert.NoError(t, err) + if err != nil { + t.Fatal(err) + } serialNumberMax := new(big.Int).Lsh(big.NewInt(1), 128) 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( KeyPair(pub, priv), @@ -38,31 +41,57 @@ func TestCA(t *testing.T) { NotBefore(time.Now().Add(time.Minute*-1)), NotAfter(time.Now().Add(time.Minute)), ) - assert.NoError(t, err, "Couldn't sign CA") - asn1Key, _ := pem.Decode(key) - 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)) + if err != nil { + t.Fatal(err) + } - pool := x509.NewCertPool() - assert.True(t, pool.AppendCertsFromPEM(cert), "Coudn't parse cert") + asn1Key, _ := pem.Decode(key) + 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) - assert.NotNil(t, asn1Cert, "Couldn't parse pem cert") - x509cert, err := x509.ParseCertificate(asn1Cert.Bytes) - assert.NoError(t, err, "Couldn't parse asn1 cert") - chains, err := x509cert.Verify(x509.VerifyOptions{ - Roots: pool, - }) - assert.NoError(t, err, "Cert didn't verify") - assert.Len(t, chains, 1, "CA should have 1 cert in chain") + if asn1Cert == nil { + t.Fatal(err) + } + + /* + pool := x509.NewCertPool() + + x509cert, err := x509.ParseCertificate(asn1Cert.Bytes) + if err != nil { + t.Fatal(err) + } + + + chains, err := x509cert.Verify(x509.VerifyOptions{ + Roots: pool, + }) + if err != nil { + t.Fatal(err) + } + + if len(chains) != 1 { + t.Fatal("CA should have 1 cert in chain") + } + */ } func TestCSR(t *testing.T) { pub, priv, err := GenerateKey() - assert.NoError(t, err) + if err != nil { + t.Fatal(err) + } + csr, err := CSR( Subject( pkix.Name{ @@ -75,16 +104,26 @@ func TestCSR(t *testing.T) { IPAddresses(net.ParseIP("127.0.0.1")), KeyPair(pub, priv), ) - assert.NoError(t, err, "CSR couldn't be encoded") + if err != nil { + t.Fatal(err) + } asn1csr, _ := pem.Decode(csr) - assert.NotNil(t, asn1csr) + if asn1csr == nil { + t.Fatal(err) + } + decodedcsr, err := x509.ParseCertificateRequest(asn1csr.Bytes) - assert.NoError(t, err) + if err != nil { + t.Fatal(err) + } + expected := pkix.Name{ CommonName: "testnode", Organization: []string{"microtest"}, 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()) + } } diff --git a/util/scope/scope.go b/util/scope/scope.go deleted file mode 100644 index f66f3ced..00000000 --- a/util/scope/scope.go +++ /dev/null @@ -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...) -}