// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package api import ( "net" "strconv" ) type Weights struct { Passing int Warning int } type Node struct { ID string Node string Address string Datacenter string TaggedAddresses map[string]string Meta map[string]string CreateIndex uint64 ModifyIndex uint64 Partition string `json:",omitempty"` PeerName string `json:",omitempty"` Locality *Locality `json:",omitempty"` } type ServiceAddress struct { Address string Port int } type CatalogService struct { ID string Node string Address string Datacenter string TaggedAddresses map[string]string NodeMeta map[string]string ServiceID string ServiceName string ServiceAddress string ServiceTaggedAddresses map[string]ServiceAddress ServiceTags []string ServiceMeta map[string]string ServicePort int ServiceWeights Weights ServiceEnableTagOverride bool ServiceProxy *AgentServiceConnectProxyConfig ServiceLocality *Locality `json:",omitempty"` CreateIndex uint64 Checks HealthChecks ModifyIndex uint64 Namespace string `json:",omitempty"` Partition string `json:",omitempty"` } type CatalogNode struct { Node *Node Services map[string]*AgentService } type CatalogNodeServiceList struct { Node *Node Services []*AgentService } type CatalogRegistration struct { ID string Node string Address string TaggedAddresses map[string]string NodeMeta map[string]string Datacenter string Service *AgentService Check *AgentCheck Checks HealthChecks SkipNodeUpdate bool Partition string `json:",omitempty"` Locality *Locality } type CatalogDeregistration struct { Node string Address string `json:",omitempty"` // Obsolete. Datacenter string ServiceID string CheckID string Namespace string `json:",omitempty"` Partition string `json:",omitempty"` } type CompoundServiceName struct { Name string // Namespacing is a Consul Enterprise feature. Namespace string `json:",omitempty"` // Partitions are a Consul Enterprise feature. Partition string `json:",omitempty"` } // GatewayService associates a gateway with a linked service. // It also contains service-specific gateway configuration like ingress listener port and protocol. type GatewayService struct { Gateway CompoundServiceName Service CompoundServiceName GatewayKind ServiceKind Port int `json:",omitempty"` Protocol string `json:",omitempty"` Hosts []string `json:",omitempty"` CAFile string `json:",omitempty"` CertFile string `json:",omitempty"` KeyFile string `json:",omitempty"` SNI string `json:",omitempty"` FromWildcard bool `json:",omitempty"` } // Catalog can be used to query the Catalog endpoints type Catalog struct { c *Client } // Catalog returns a handle to the catalog endpoints func (c *Client) Catalog() *Catalog { return &Catalog{c} } func (c *Catalog) Register(reg *CatalogRegistration, q *WriteOptions) (*WriteMeta, error) { r := c.c.newRequest("PUT", "/v1/catalog/register") r.setWriteOptions(q) r.obj = reg rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, err } wm := &WriteMeta{} wm.RequestTime = rtt return wm, nil } func (c *Catalog) Deregister(dereg *CatalogDeregistration, q *WriteOptions) (*WriteMeta, error) { r := c.c.newRequest("PUT", "/v1/catalog/deregister") r.setWriteOptions(q) r.obj = dereg rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, err } wm := &WriteMeta{} wm.RequestTime = rtt return wm, nil } // Datacenters is used to query for all the known datacenters func (c *Catalog) Datacenters() ([]string, error) { r := c.c.newRequest("GET", "/v1/catalog/datacenters") _, resp, err := c.c.doRequest(r) if err != nil { return nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, err } var out []string if err := decodeBody(resp, &out); err != nil { return nil, err } return out, nil } // Nodes is used to query all the known nodes func (c *Catalog) Nodes(q *QueryOptions) ([]*Node, *QueryMeta, error) { r := c.c.newRequest("GET", "/v1/catalog/nodes") r.setQueryOptions(q) rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out []*Node if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return out, qm, nil } // Services is used to query for all known services func (c *Catalog) Services(q *QueryOptions) (map[string][]string, *QueryMeta, error) { r := c.c.newRequest("GET", "/v1/catalog/services") r.setQueryOptions(q) rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out map[string][]string if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return out, qm, nil } // Service is used to query catalog entries for a given service func (c *Catalog) Service(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) { var tags []string if tag != "" { tags = []string{tag} } return c.service(service, tags, q, false) } // Supports multiple tags for filtering func (c *Catalog) ServiceMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) { return c.service(service, tags, q, false) } // Connect is used to query catalog entries for a given Connect-enabled service func (c *Catalog) Connect(service, tag string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) { var tags []string if tag != "" { tags = []string{tag} } return c.service(service, tags, q, true) } // Supports multiple tags for filtering func (c *Catalog) ConnectMultipleTags(service string, tags []string, q *QueryOptions) ([]*CatalogService, *QueryMeta, error) { return c.service(service, tags, q, true) } func (c *Catalog) service(service string, tags []string, q *QueryOptions, connect bool) ([]*CatalogService, *QueryMeta, error) { path := "/v1/catalog/service/" + service if connect { path = "/v1/catalog/connect/" + service } r := c.c.newRequest("GET", path) r.setQueryOptions(q) if len(tags) > 0 { for _, tag := range tags { r.params.Add("tag", tag) } } rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out []*CatalogService if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return out, qm, nil } // Node is used to query for service information about a single node func (c *Catalog) Node(node string, q *QueryOptions) (*CatalogNode, *QueryMeta, error) { r := c.c.newRequest("GET", "/v1/catalog/node/"+node) r.setQueryOptions(q) rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out *CatalogNode if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return out, qm, nil } // NodeServiceList is used to query for service information about a single node. It differs from // the Node function only in its return type which will contain a list of services as opposed to // a map of service ids to services. This different structure allows for using the wildcard specifier // '*' for the Namespace in the QueryOptions. func (c *Catalog) NodeServiceList(node string, q *QueryOptions) (*CatalogNodeServiceList, *QueryMeta, error) { r := c.c.newRequest("GET", "/v1/catalog/node-services/"+node) r.setQueryOptions(q) rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out *CatalogNodeServiceList if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return out, qm, nil } // GatewayServices is used to query the services associated with an ingress gateway or terminating gateway. func (c *Catalog) GatewayServices(gateway string, q *QueryOptions) ([]*GatewayService, *QueryMeta, error) { r := c.c.newRequest("GET", "/v1/catalog/gateway-services/"+gateway) r.setQueryOptions(q) rtt, resp, err := c.c.doRequest(r) if err != nil { return nil, nil, err } defer closeResponseBody(resp) if err := requireOK(resp); err != nil { return nil, nil, err } qm := &QueryMeta{} parseQueryMeta(resp, qm) qm.RequestTime = rtt var out []*GatewayService if err := decodeBody(resp, &out); err != nil { return nil, nil, err } return out, qm, nil } func ParseServiceAddr(addrPort string) (ServiceAddress, error) { port := 0 host, portStr, err := net.SplitHostPort(addrPort) if err == nil { port, err = strconv.Atoi(portStr) } return ServiceAddress{Address: host, Port: port}, err }