package registry import ( "crypto/tls" "errors" "fmt" "net" "net/http" "runtime" "time" consul "github.com/hashicorp/consul/api" ) type consulRegistry struct { Address string Client *consul.Client Options Options } func newTransport(config *tls.Config) *http.Transport { if config == nil { config = &tls.Config{ InsecureSkipVerify: true, } } t := &http.Transport{ Proxy: http.ProxyFromEnvironment, Dial: (&net.Dialer{ Timeout: 30 * time.Second, KeepAlive: 30 * time.Second, }).Dial, TLSHandshakeTimeout: 10 * time.Second, TLSClientConfig: config, } runtime.SetFinalizer(&t, func(tr **http.Transport) { (*tr).CloseIdleConnections() }) return t } func newConsulRegistry(opts ...Option) Registry { var options Options for _, o := range opts { o(&options) } // use default config config := consul.DefaultConfig() // set timeout if options.Timeout > 0 { config.HttpClient.Timeout = options.Timeout } // check if there are any addrs if len(options.Addrs) > 0 { addr, port, err := net.SplitHostPort(options.Addrs[0]) if ae, ok := err.(*net.AddrError); ok && ae.Err == "missing port in address" { port = "8500" addr = options.Addrs[0] config.Address = fmt.Sprintf("%s:%s", addr, port) } else if err == nil { config.Address = fmt.Sprintf("%s:%s", addr, port) } } // requires secure connection? if options.Secure || options.TLSConfig != nil { config.Scheme = "https" // We're going to support InsecureSkipVerify config.HttpClient.Transport = newTransport(options.TLSConfig) } // create the client client, _ := consul.NewClient(config) cr := &consulRegistry{ Address: config.Address, Client: client, Options: options, } return cr } func (c *consulRegistry) Deregister(s *Service) error { if len(s.Nodes) == 0 { return errors.New("Require at least one node") } node := s.Nodes[0] return c.Client.Agent().ServiceDeregister(node.Id) } func (c *consulRegistry) Register(s *Service, opts ...RegisterOption) error { if len(s.Nodes) == 0 { return errors.New("Require at least one node") } var options RegisterOptions for _, o := range opts { o(&options) } node := s.Nodes[0] tags := encodeMetadata(node.Metadata) tags = append(tags, encodeEndpoints(s.Endpoints)...) tags = append(tags, encodeVersion(s.Version)...) var check *consul.AgentServiceCheck if options.TTL > time.Duration(0) { check = &consul.AgentServiceCheck{ TTL: fmt.Sprintf("%v", options.TTL), } } if err := c.Client.Agent().ServiceRegister(&consul.AgentServiceRegistration{ ID: node.Id, Name: s.Name, Tags: tags, Port: node.Port, Address: node.Address, Check: check, }); err != nil { return err } if options.TTL == time.Duration(0) { return nil } return c.Client.Agent().PassTTL("service:"+node.Id, "") } func (c *consulRegistry) GetService(name string) ([]*Service, error) { rsp, _, err := c.Client.Health().Service(name, "", true, nil) if err != nil { return nil, err } serviceMap := map[string]*Service{} for _, s := range rsp { if s.Service.Service != name { continue } // version is now a tag version, found := decodeVersion(s.Service.Tags) // service ID is now the node id id := s.Service.ID // key is always the version key := version // address is service address address := s.Service.Address // if we can't get the new type of version // use old the old ways if !found { // id was set as node id = s.Node.Node // key was service id key = s.Service.ID // version was service id version = s.Service.ID // address was address address = s.Node.Address } svc, ok := serviceMap[key] if !ok { svc = &Service{ Endpoints: decodeEndpoints(s.Service.Tags), Name: s.Service.Service, Version: version, } serviceMap[key] = svc } svc.Nodes = append(svc.Nodes, &Node{ Id: id, Address: address, Port: s.Service.Port, Metadata: decodeMetadata(s.Service.Tags), }) } var services []*Service for _, service := range serviceMap { services = append(services, service) } return services, nil } func (c *consulRegistry) ListServices() ([]*Service, error) { rsp, _, err := c.Client.Catalog().Services(nil) if err != nil { return nil, err } var services []*Service for service, _ := range rsp { services = append(services, &Service{Name: service}) } return services, nil } func (c *consulRegistry) Watch() (Watcher, error) { return newConsulWatcher(c) } func (c *consulRegistry) String() string { return "consul" }