internal/agent/catalog_endpoint.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package agent
import (
"fmt"
"net/http"
"strings"
"git.j3s.sh/cascade/api"
)
// catalogServices serves GET /v1/catalog/services. Returns the cluster-wide
// map of service name -> union of tags seen on any instance.
func (s *HTTPHandlers) catalogServices(w http.ResponseWriter, r *http.Request) {
out := s.agent.CatalogServices()
if out == nil {
out = map[string][]string{}
}
s.writeJSON(w, r, out)
}
// catalogService serves GET /v1/catalog/service/<name>. Returns one entry
// per known instance, joined with the owning node's serf membership info.
func (s *HTTPHandlers) catalogService(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/v1/catalog/service/")
if name == "" || strings.Contains(name, "/") {
http.Error(w, "service name required in path", http.StatusBadRequest)
return
}
instances := s.agent.CatalogServiceInstances(name)
out := make([]api.CatalogService, 0, len(instances))
for _, inst := range instances {
entry := api.CatalogService{
Node: inst.Node,
ServiceID: inst.Service.ID,
ServiceName: inst.Service.Service,
ServiceAddress: inst.Service.Address,
ServicePort: inst.Service.Port,
ServiceTags: inst.Service.Tags,
ServiceMeta: inst.Service.Meta,
}
if m, ok := s.agent.MemberByName(inst.Node); ok {
entry.Address = m.Addr.String()
}
out = append(out, entry)
}
s.writeJSON(w, r, out)
}
// catalogNodes serves GET /v1/catalog/nodes. Returns the serf membership
// list shaped as Consul-style Node entries.
func (s *HTTPHandlers) catalogNodes(w http.ResponseWriter, r *http.Request) {
members := s.agent.CatalogNodes()
out := make([]api.Node, 0, len(members))
for _, m := range members {
out = append(out, api.Node{
Node: m.Name,
Address: m.Addr.String(),
Meta: m.Tags,
})
}
s.writeJSON(w, r, out)
}
// catalogNode serves GET /v1/catalog/node/<name>. Returns the node and the
// services it owns.
func (s *HTTPHandlers) catalogNode(w http.ResponseWriter, r *http.Request) {
name := strings.TrimPrefix(r.URL.Path, "/v1/catalog/node/")
if name == "" || strings.Contains(name, "/") {
http.Error(w, "node name required in path", http.StatusBadRequest)
return
}
out := api.CatalogNode{
Services: s.agent.CatalogNodeServices(name),
}
if out.Services == nil {
out.Services = map[string]*api.AgentService{}
}
if m, ok := s.agent.MemberByName(name); ok {
out.Node = &api.Node{
Node: m.Name,
Address: m.Addr.String(),
Meta: m.Tags,
}
} else if len(out.Services) > 0 {
// Node has gossipped services but serf hasn't surfaced it yet.
out.Node = &api.Node{Node: name}
} else {
http.Error(w, fmt.Sprintf("unknown node %q", name), http.StatusNotFound)
return
}
s.writeJSON(w, r, out)
}
// writeJSON is the standard 200 + application/json response helper.
func (s *HTTPHandlers) writeJSON(w http.ResponseWriter, r *http.Request, obj interface{}) {
buf, err := s.marshalJSON(r, obj)
if err != nil {
s.agent.logger.Error("marshal response", err)
w.WriteHeader(http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
w.Write(buf)
}