File: watch.go

package info (click to toggle)
golang-github-spiffe-go-spiffe 2.5.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,116 kB
  • sloc: makefile: 157
file content (79 lines) | stat: -rw-r--r-- 2,550 bytes parent folder | download | duplicates (2)
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
package federation

import (
	"context"
	"time"

	"github.com/spiffe/go-spiffe/v2/bundle/spiffebundle"
	"github.com/spiffe/go-spiffe/v2/spiffeid"
)

// BundleWatcher is used by WatchBundle to provide the caller with bundle updates and
// control the next refresh time.
type BundleWatcher interface {
	// NextRefresh is called by WatchBundle to determine when the next refresh
	// should take place. A refresh hint is provided, which can be zero, meaning
	// the watcher is free to choose its own refresh cadence. If the refresh hint
	// is greater than zero, the watcher SHOULD return a next refresh time at or
	// below that to ensure the bundle stays up-to-date.
	NextRefresh(refreshHint time.Duration) time.Duration

	// OnUpdate is called when a bundle has been updated. If a bundle is
	// fetched but has not changed from the previously fetched bundle, OnUpdate
	// will not be called. This function is called synchronously by WatchBundle
	// and therefore should have a short execution time to prevent blocking the
	// watch.
	OnUpdate(*spiffebundle.Bundle)

	// OnError is called if there is an error fetching the bundle from the
	// endpoint. This function is called synchronously by WatchBundle
	// and therefore should have a short execution time to prevent blocking the
	// watch.
	OnError(err error)
}

// WatchBundle watches a bundle on a bundle endpoint. It returns when the
// context is canceled, returning ctx.Err().
func WatchBundle(ctx context.Context, trustDomain spiffeid.TrustDomain, url string, watcher BundleWatcher, options ...FetchOption) error {
	if watcher == nil {
		return federationErr.New("watcher cannot be nil")
	}

	latestBundle := &spiffebundle.Bundle{}
	var timer *time.Timer
	for {
		bundle, err := FetchBundle(ctx, trustDomain, url, options...)
		switch {
		// Context was canceled when fetching bundle, so to avoid
		// more calls to FetchBundle (because the timer could be expired at
		// this point) we return now.
		case ctx.Err() == context.Canceled:
			return ctx.Err()
		case err != nil:
			watcher.OnError(err)
		case !latestBundle.Equal(bundle):
			watcher.OnUpdate(bundle)
			latestBundle = bundle
		}

		var nextRefresh time.Duration
		if refreshHint, ok := latestBundle.RefreshHint(); ok {
			nextRefresh = watcher.NextRefresh(refreshHint)
		} else {
			nextRefresh = watcher.NextRefresh(0)
		}

		if timer == nil {
			timer = time.NewTimer(nextRefresh)
			defer timer.Stop()
		} else {
			timer.Reset(nextRefresh)
		}

		select {
		case <-timer.C:
		case <-ctx.Done():
			return ctx.Err()
		}
	}
}