package dependency import ( "encoding/gob" "errors" "fmt" "log" "regexp" "sort" ) func init() { gob.Register([]*NodeDetail{}) gob.Register([]*NodeService{}) } type NodeDetail struct { Node *Node Services NodeServiceList } type NodeService struct { ID string Service string Tags ServiceTags Port int } type CatalogNode struct { rawKey string dataCenter string } // Fetch queries the Consul API defined by the given client and returns a // of NodeDetail object func (d *CatalogNode) Fetch(clients *ClientSet, opts *QueryOptions) (interface{}, *ResponseMetadata, error) { if opts == nil { opts = &QueryOptions{} } consulOpts := opts.consulQueryOptions() if d.dataCenter != "" { consulOpts.Datacenter = d.dataCenter } log.Printf("[DEBUG] (%s) querying Consul with %+v", d.Display(), consulOpts) consul, err := clients.Consul() if err != nil { return nil, nil, fmt.Errorf("catalog node: error getting client: %s", err) } nodeName := d.rawKey if nodeName == "" { log.Printf("[DEBUG] (%s) getting local agent name", d.Display()) nodeName, err = consul.Agent().NodeName() if err != nil { return nil, nil, fmt.Errorf("catalog node: error getting local agent: %s", err) } } catalog := consul.Catalog() n, qm, err := catalog.Node(nodeName, consulOpts) if err != nil { return nil, nil, fmt.Errorf("catalog node: error fetching: %s", err) } rm := &ResponseMetadata{ LastIndex: qm.LastIndex, LastContact: qm.LastContact, } if n == nil { log.Printf("[WARN] (%s) could not find node by that name", d.Display()) var node *NodeDetail return node, rm, nil } services := make(NodeServiceList, 0, len(n.Services)) for _, v := range n.Services { services = append(services, &NodeService{ ID: v.ID, Service: v.Service, Tags: ServiceTags(deepCopyAndSortTags(v.Tags)), Port: v.Port, }) } sort.Stable(services) node := &NodeDetail{ Node: &Node{ Node: n.Node.Node, Address: n.Node.Address, }, Services: services, } return node, rm, nil } // CanShare returns if this dependency is shareable. func (d *CatalogNode) CanShare() bool { return true } func (d *CatalogNode) HashCode() string { if d.dataCenter != "" { return fmt.Sprintf("NodeDetail|%s@%s", d.rawKey, d.dataCenter) } return fmt.Sprintf("NodeDetail|%s", d.rawKey) } func (d *CatalogNode) Display() string { if d.dataCenter != "" { return fmt.Sprintf("node(%s@%s)", d.rawKey, d.dataCenter) } return fmt.Sprintf(`"node(%s)"`, d.rawKey) } // ParseCatalogNode parses a name name and optional datacenter value. // If the name is empty or not provided then the current agent is used. func ParseCatalogNode(s ...string) (*CatalogNode, error) { switch len(s) { case 0: return &CatalogNode{}, nil case 1: return &CatalogNode{rawKey: s[0]}, nil case 2: dc := s[1] re := regexp.MustCompile(`\A` + `(@(?P[[:word:]\.\-]+))?` + `\z`) names := re.SubexpNames() match := re.FindAllStringSubmatch(dc, -1) if len(match) == 0 { return nil, errors.New("invalid node dependency format") } r := match[0] m := map[string]string{} for i, n := range r { if names[i] != "" { m[names[i]] = n } } nd := &CatalogNode{ rawKey: s[0], dataCenter: m["datacenter"], } return nd, nil default: return nil, fmt.Errorf("expected 0, 1, or 2 arguments, got %d", len(s)) } } // Sorting type NodeServiceList []*NodeService func (s NodeServiceList) Len() int { return len(s) } func (s NodeServiceList) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s NodeServiceList) Less(i, j int) bool { if s[i].Service == s[j].Service { return s[i].ID <= s[j].ID } return s[i].Service <= s[j].Service }