@@ -6,13 +6,16 @@ import (
66 "fmt"
77 "log"
88 "os"
9+ "regexp"
910
1011 "github.com/hashicorp/consul/api"
1112 "github.com/olekukonko/tablewriter"
1213)
1314
1415// this is the default port for talking to remote consul agents
15- const defaultPort = 8500
16+ const (
17+ defaultPort = 8500
18+ )
1619
1720type verbosityLevel uint8
1821
@@ -38,15 +41,16 @@ func usage(code int) {
3841}
3942
4043func main () {
41- fs := flag .NewFlagSet (os .Args [0 ], flag .ExitOnError )
44+ fs := flag .NewFlagSet (os .Args [0 ], flag .ContinueOnError )
4245 serviceString := fs .String ("s" , "" , "Limit search by service address (regexp)" )
4346 tag := fs .String ("t" , "" , "Limit search by tag" )
4447 force := fs .Bool ("f" , false , "Force killing of all matches, including healthy services" )
45- port := fs .Int ("port" , defaultPort , "Port to use when connecting to remote agents" )
46- token := fs .String ("token" , "" , "Token to use when connecting to remote agents" )
48+ localAddr := fs .String ("local-addr" , os .Getenv (api .HTTPAddrEnvName ), "Address with port of \" local\" agent. Used to list services." )
49+ remotePort := fs .Int ("remote-port" , defaultPort , "Port to use when connecting to remote agents" )
50+ token := fs .String ("token" , os .Getenv (api .HTTPTokenEnvName ), "ACL token. Used in all api queries." )
4751 v1 := fs .Bool ("v" , false , "Verbose" )
48- v2 := fs .Bool ("vv" , false , "Increased Verbosity " )
49- v3 := fs .Bool ("vvv" , false , "Super Verbosity " )
52+ v2 := fs .Bool ("vv" , false , "Really verbose " )
53+ v3 := fs .Bool ("vvv" , false , "Extremely verbose " )
5054 if err := fs .Parse (os .Args [1 :]); err != nil {
5155 log .Printf ("Error parsing args: %s" , err )
5256 os .Exit (1 )
@@ -71,19 +75,111 @@ func main() {
7175 switch cmd {
7276 // define a couple synonyms to "hunt" as well
7377 case "hunt" , "find" , "search" :
74- serviceList := getList (* serviceString , * token , * tag )
78+ serviceList := getList (* localAddr , * token , * serviceString , * tag )
7579 printList (serviceList , verbosity )
7680
7781 case "kill" :
78- serviceList := getList (* serviceString , * token , * tag )
79- deregister (serviceList , * port , * token , * force )
82+ serviceList := getList (* localAddr , * token , * serviceString , * tag )
83+ deregister (* remotePort , * token , serviceList , * force )
8084
8185 default :
8286 usage (1 )
8387 }
8488
8589}
8690
91+ // get a client handle for a specified address (or the local agent if "")
92+ func getClient (address , token string ) (* api.Client , error ) {
93+ config := api .DefaultConfig ()
94+ if address != "" {
95+ config .Address = address
96+ }
97+ if token != "" {
98+ config .Token = token
99+ }
100+ return api .NewClient (config )
101+ }
102+
103+ // get a list of all services, limit to those matching the search criteria
104+ func getList (localAddr , token , serviceString , tag string ) []* api.ServiceEntry {
105+ client , err := getClient (localAddr , token )
106+ if err != nil {
107+ log .Fatalf ("Unable to get a consul client connection: %s\n " , err )
108+ }
109+
110+ serviceList , _ , err := client .Catalog ().Services (nil )
111+ if err != nil {
112+ log .Fatalf ("Unable to get list of services from catalog: %s" , err )
113+ }
114+
115+ nodeServiceMap := make (map [string ]map [string ]* api.ServiceEntry )
116+
117+ for svc := range serviceList {
118+ entries , _ , err := client .Health ().Service (svc , tag , false , nil )
119+ if err != nil {
120+ log .Fatalf ("Unable to query for service \" %s\" health: %s" , svc , err )
121+ }
122+ for _ , entry := range entries {
123+ if _ , ok := nodeServiceMap [entry .Node .Node ]; ! ok {
124+ nodeServiceMap [entry .Node .Node ] = make (map [string ]* api.ServiceEntry )
125+ }
126+ nodeServiceMap [entry .Node .Node ][entry .Service .ID ] = entry
127+ }
128+ }
129+
130+ seOut := make ([]* api.ServiceEntry , 0 )
131+ if serviceString == "" {
132+ for _ , services := range nodeServiceMap {
133+ for _ , service := range services {
134+ seOut = append (seOut , service )
135+ }
136+ }
137+ } else {
138+ var (
139+ serviceID string
140+ services map [string ]* api.ServiceEntry
141+ service * api.ServiceEntry
142+ )
143+ re := regexp .MustCompile (serviceString )
144+ for _ , services = range nodeServiceMap {
145+ for serviceID , service = range services {
146+ if re .FindString (serviceID ) != "" || re .FindString (service .Service .Service ) != "" {
147+ seOut = append (seOut , service )
148+ }
149+ }
150+ }
151+ }
152+
153+ return seOut
154+ }
155+
156+ // A service entry is considered healty if all the eligible checks are passing.
157+ // serfChecks are not eligible
158+ func isHealthy (se * api.ServiceEntry ) bool {
159+ if se == nil || se .Checks == nil || len (se .Checks ) == 0 {
160+ // No checks = failing
161+ return false
162+ }
163+
164+ healthy := true
165+ eligible := 0
166+ for _ , c := range se .Checks {
167+ if c .Name == "serfHealth" {
168+ continue
169+ }
170+ // All found checks have to be passing
171+ healthy = healthy && (c .Status == api .HealthPassing )
172+ eligible ++
173+ }
174+
175+ // No eligible checks were found
176+ if eligible == 0 {
177+ return false
178+ }
179+
180+ return healthy
181+ }
182+
87183// display a list of matching services
88184func printList (serviceList []* api.ServiceEntry , v verbosityLevel ) {
89185 var header , footer []string
@@ -142,10 +238,10 @@ func printList(serviceList []*api.ServiceEntry, v verbosityLevel) {
142238}
143239
144240// kill those services that are failing in the passed list, or all if force is true
145- func deregister (serviceList []* api.ServiceEntry , port int , token string , force bool ) {
241+ func deregister (remotePort int , token string , serviceList []* api.ServiceEntry , force bool ) {
146242 for _ , se := range serviceList {
147243 if ! isHealthy (se ) || force {
148- fullAddress := fmt .Sprintf ("%s:%d" , se .Node .Address , port )
244+ fullAddress := fmt .Sprintf ("%s:%d" , se .Node .Address , remotePort )
149245 fmt .Printf ("Deregistering %s: %s (%s)\n " , se .Service .Service , se .Service .ID , fullAddress )
150246 client , err := getClient (fullAddress , token )
151247 if err != nil {
0 commit comments