github.com/kjdelisle/consul@v1.4.5/watch/funcs.go (about) 1 package watch 2 3 import ( 4 "context" 5 "fmt" 6 7 consulapi "github.com/hashicorp/consul/api" 8 ) 9 10 // watchFactory is a function that can create a new WatchFunc 11 // from a parameter configuration 12 type watchFactory func(params map[string]interface{}) (WatcherFunc, error) 13 14 // watchFuncFactory maps each type to a factory function 15 var watchFuncFactory map[string]watchFactory 16 17 func init() { 18 watchFuncFactory = map[string]watchFactory{ 19 "key": keyWatch, 20 "keyprefix": keyPrefixWatch, 21 "services": servicesWatch, 22 "nodes": nodesWatch, 23 "service": serviceWatch, 24 "checks": checksWatch, 25 "event": eventWatch, 26 "connect_roots": connectRootsWatch, 27 "connect_leaf": connectLeafWatch, 28 "connect_proxy_config": connectProxyConfigWatch, 29 "agent_service": agentServiceWatch, 30 } 31 } 32 33 // keyWatch is used to return a key watching function 34 func keyWatch(params map[string]interface{}) (WatcherFunc, error) { 35 stale := false 36 if err := assignValueBool(params, "stale", &stale); err != nil { 37 return nil, err 38 } 39 40 var key string 41 if err := assignValue(params, "key", &key); err != nil { 42 return nil, err 43 } 44 if key == "" { 45 return nil, fmt.Errorf("Must specify a single key to watch") 46 } 47 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 48 kv := p.client.KV() 49 opts := makeQueryOptionsWithContext(p, stale) 50 defer p.cancelFunc() 51 pair, meta, err := kv.Get(key, &opts) 52 if err != nil { 53 return nil, nil, err 54 } 55 if pair == nil { 56 return WaitIndexVal(meta.LastIndex), nil, err 57 } 58 return WaitIndexVal(meta.LastIndex), pair, err 59 } 60 return fn, nil 61 } 62 63 // keyPrefixWatch is used to return a key prefix watching function 64 func keyPrefixWatch(params map[string]interface{}) (WatcherFunc, error) { 65 stale := false 66 if err := assignValueBool(params, "stale", &stale); err != nil { 67 return nil, err 68 } 69 70 var prefix string 71 if err := assignValue(params, "prefix", &prefix); err != nil { 72 return nil, err 73 } 74 if prefix == "" { 75 return nil, fmt.Errorf("Must specify a single prefix to watch") 76 } 77 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 78 kv := p.client.KV() 79 opts := makeQueryOptionsWithContext(p, stale) 80 defer p.cancelFunc() 81 pairs, meta, err := kv.List(prefix, &opts) 82 if err != nil { 83 return nil, nil, err 84 } 85 return WaitIndexVal(meta.LastIndex), pairs, err 86 } 87 return fn, nil 88 } 89 90 // servicesWatch is used to watch the list of available services 91 func servicesWatch(params map[string]interface{}) (WatcherFunc, error) { 92 stale := false 93 if err := assignValueBool(params, "stale", &stale); err != nil { 94 return nil, err 95 } 96 97 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 98 catalog := p.client.Catalog() 99 opts := makeQueryOptionsWithContext(p, stale) 100 defer p.cancelFunc() 101 services, meta, err := catalog.Services(&opts) 102 if err != nil { 103 return nil, nil, err 104 } 105 return WaitIndexVal(meta.LastIndex), services, err 106 } 107 return fn, nil 108 } 109 110 // nodesWatch is used to watch the list of available nodes 111 func nodesWatch(params map[string]interface{}) (WatcherFunc, error) { 112 stale := false 113 if err := assignValueBool(params, "stale", &stale); err != nil { 114 return nil, err 115 } 116 117 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 118 catalog := p.client.Catalog() 119 opts := makeQueryOptionsWithContext(p, stale) 120 defer p.cancelFunc() 121 nodes, meta, err := catalog.Nodes(&opts) 122 if err != nil { 123 return nil, nil, err 124 } 125 return WaitIndexVal(meta.LastIndex), nodes, err 126 } 127 return fn, nil 128 } 129 130 // serviceWatch is used to watch a specific service for changes 131 func serviceWatch(params map[string]interface{}) (WatcherFunc, error) { 132 stale := false 133 if err := assignValueBool(params, "stale", &stale); err != nil { 134 return nil, err 135 } 136 137 var service, tag string 138 if err := assignValue(params, "service", &service); err != nil { 139 return nil, err 140 } 141 if service == "" { 142 return nil, fmt.Errorf("Must specify a single service to watch") 143 } 144 145 if err := assignValue(params, "tag", &tag); err != nil { 146 return nil, err 147 } 148 149 passingOnly := false 150 if err := assignValueBool(params, "passingonly", &passingOnly); err != nil { 151 return nil, err 152 } 153 154 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 155 health := p.client.Health() 156 opts := makeQueryOptionsWithContext(p, stale) 157 defer p.cancelFunc() 158 nodes, meta, err := health.Service(service, tag, passingOnly, &opts) 159 if err != nil { 160 return nil, nil, err 161 } 162 return WaitIndexVal(meta.LastIndex), nodes, err 163 } 164 return fn, nil 165 } 166 167 // checksWatch is used to watch a specific checks in a given state 168 func checksWatch(params map[string]interface{}) (WatcherFunc, error) { 169 stale := false 170 if err := assignValueBool(params, "stale", &stale); err != nil { 171 return nil, err 172 } 173 174 var service, state string 175 if err := assignValue(params, "service", &service); err != nil { 176 return nil, err 177 } 178 if err := assignValue(params, "state", &state); err != nil { 179 return nil, err 180 } 181 if service != "" && state != "" { 182 return nil, fmt.Errorf("Cannot specify service and state") 183 } 184 if service == "" && state == "" { 185 state = "any" 186 } 187 188 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 189 health := p.client.Health() 190 opts := makeQueryOptionsWithContext(p, stale) 191 defer p.cancelFunc() 192 var checks []*consulapi.HealthCheck 193 var meta *consulapi.QueryMeta 194 var err error 195 if state != "" { 196 checks, meta, err = health.State(state, &opts) 197 } else { 198 checks, meta, err = health.Checks(service, &opts) 199 } 200 if err != nil { 201 return nil, nil, err 202 } 203 return WaitIndexVal(meta.LastIndex), checks, err 204 } 205 return fn, nil 206 } 207 208 // eventWatch is used to watch for events, optionally filtering on name 209 func eventWatch(params map[string]interface{}) (WatcherFunc, error) { 210 // The stale setting doesn't apply to events. 211 212 var name string 213 if err := assignValue(params, "name", &name); err != nil { 214 return nil, err 215 } 216 217 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 218 event := p.client.Event() 219 opts := makeQueryOptionsWithContext(p, false) 220 defer p.cancelFunc() 221 events, meta, err := event.List(name, &opts) 222 if err != nil { 223 return nil, nil, err 224 } 225 226 // Prune to only the new events 227 for i := 0; i < len(events); i++ { 228 if WaitIndexVal(event.IDToIndex(events[i].ID)).Equal(p.lastParamVal) { 229 events = events[i+1:] 230 break 231 } 232 } 233 return WaitIndexVal(meta.LastIndex), events, err 234 } 235 return fn, nil 236 } 237 238 // connectRootsWatch is used to watch for changes to Connect Root certificates. 239 func connectRootsWatch(params map[string]interface{}) (WatcherFunc, error) { 240 // We don't support stale since roots are cached locally in the agent. 241 242 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 243 agent := p.client.Agent() 244 opts := makeQueryOptionsWithContext(p, false) 245 defer p.cancelFunc() 246 247 roots, meta, err := agent.ConnectCARoots(&opts) 248 if err != nil { 249 return nil, nil, err 250 } 251 252 return WaitIndexVal(meta.LastIndex), roots, err 253 } 254 return fn, nil 255 } 256 257 // connectLeafWatch is used to watch for changes to Connect Leaf certificates 258 // for given local service id. 259 func connectLeafWatch(params map[string]interface{}) (WatcherFunc, error) { 260 // We don't support stale since certs are cached locally in the agent. 261 262 var serviceName string 263 if err := assignValue(params, "service", &serviceName); err != nil { 264 return nil, err 265 } 266 267 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 268 agent := p.client.Agent() 269 opts := makeQueryOptionsWithContext(p, false) 270 defer p.cancelFunc() 271 272 leaf, meta, err := agent.ConnectCALeaf(serviceName, &opts) 273 if err != nil { 274 return nil, nil, err 275 } 276 277 return WaitIndexVal(meta.LastIndex), leaf, err 278 } 279 return fn, nil 280 } 281 282 // connectProxyConfigWatch is used to watch for changes to Connect managed proxy 283 // configuration. Note that this state is agent-local so the watch mechanism 284 // uses `hash` rather than `index` for deciding whether to block. 285 func connectProxyConfigWatch(params map[string]interface{}) (WatcherFunc, error) { 286 // We don't support consistency modes since it's agent local data 287 288 var proxyServiceID string 289 if err := assignValue(params, "proxy_service_id", &proxyServiceID); err != nil { 290 return nil, err 291 } 292 293 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 294 agent := p.client.Agent() 295 opts := makeQueryOptionsWithContext(p, false) 296 defer p.cancelFunc() 297 298 config, _, err := agent.ConnectProxyConfig(proxyServiceID, &opts) 299 if err != nil { 300 return nil, nil, err 301 } 302 303 // Return string ContentHash since we don't have Raft indexes to block on. 304 return WaitHashVal(config.ContentHash), config, err 305 } 306 return fn, nil 307 } 308 309 // agentServiceWatch is used to watch for changes to a single service instance 310 // on the local agent. Note that this state is agent-local so the watch 311 // mechanism uses `hash` rather than `index` for deciding whether to block. 312 func agentServiceWatch(params map[string]interface{}) (WatcherFunc, error) { 313 // We don't support consistency modes since it's agent local data 314 315 var serviceID string 316 if err := assignValue(params, "service_id", &serviceID); err != nil { 317 return nil, err 318 } 319 320 fn := func(p *Plan) (BlockingParamVal, interface{}, error) { 321 agent := p.client.Agent() 322 opts := makeQueryOptionsWithContext(p, false) 323 defer p.cancelFunc() 324 325 svc, _, err := agent.Service(serviceID, &opts) 326 if err != nil { 327 return nil, nil, err 328 } 329 330 // Return string ContentHash since we don't have Raft indexes to block on. 331 return WaitHashVal(svc.ContentHash), svc, err 332 } 333 return fn, nil 334 } 335 336 func makeQueryOptionsWithContext(p *Plan, stale bool) consulapi.QueryOptions { 337 ctx, cancel := context.WithCancel(context.Background()) 338 p.setCancelFunc(cancel) 339 opts := consulapi.QueryOptions{AllowStale: stale} 340 switch param := p.lastParamVal.(type) { 341 case WaitIndexVal: 342 opts.WaitIndex = uint64(param) 343 case WaitHashVal: 344 opts.WaitHash = string(param) 345 } 346 return *opts.WithContext(ctx) 347 }