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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package agent
import (
"fmt"
"net/http"
"strings"
"j3s.sh/cascade/api"
)
// catalogInstances serves GET /v1/catalog/instances. Returns one entry
// per (node, service) pair across the whole cluster.
func (s *HTTPHandlers) catalogInstances(w http.ResponseWriter, r *http.Request) {
instances := s.agent.CatalogAllInstances()
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)
}
// 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)
}