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  }