• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pomerium / pomerium / 20081923205

09 Dec 2025 11:30PM UTC coverage: 52.874% (+0.08%) from 52.794%
20081923205

push

github

web-flow
add incremental config diffing for route changes (#5978)

## Summary

Add `ConfigDiffer` utility that computes incremental diffs between
config updates, emitting batched route events (upserted/deleted) via
callback.

## Related issues

<!-- For example...
- #159
-->

## User Explanation

<!-- How would you explain this change to the user? If this
change doesn't create any user-facing changes, you can leave
this blank. If filled out, add the `docs` label -->

## Checklist

- [ ] reference any related issues
- [ ] updated unit tests
- [ ] add appropriate label (`enhancement`, `bug`, `breaking`,
`dependencies`, `ci`)
- [ ] ready for review

76 of 77 new or added lines in 1 file covered. (98.7%)

5 existing lines in 3 files now uncovered.

29238 of 55297 relevant lines covered (52.87%)

84.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.7
/config/diff/config_differ.go
1
// Package diff provides incremental diffing of pomerium configuration.
2
package diff
3

4
import (
5
        "context"
6
        "sync/atomic"
7

8
        "github.com/pomerium/pomerium/config"
9
)
10

11
type RouteEventKind uint8
12

13
const (
14
        RouteDeleted RouteEventKind = iota
15
        RouteUpserted
16
)
17

18
type RouteEvent struct {
19
        Kind    RouteEventKind
20
        RouteID string
21
        Policy  *config.Policy // Only set for RouteUpserted
22
}
23

24
type OnRouteEvents func([]RouteEvent)
25

26
type ConfigDiffer struct {
27
        hashFn   func(*config.Policy) uint64
28
        filterFn func(*config.Policy) bool
29
        onEvents OnRouteEvents
30

31
        currentConfig atomic.Pointer[config.Config]
32
        wakeC         chan struct{}
33

34
        prev map[string]uint64 // routeID → hash
35
}
36

37
type Option func(*ConfigDiffer)
38

39
// WithHashFunc sets a custom hash function for determining route changes.
40
func WithHashFunc(fn func(*config.Policy) uint64) Option {
1✔
41
        return func(d *ConfigDiffer) {
2✔
42
                d.hashFn = fn
1✔
43
        }
1✔
44
}
45

46
// WithFilterFunc sets a custom filter function for selecting which policies
47
// to include in diff computations.
48
func WithFilterFunc(fn func(*config.Policy) bool) Option {
2✔
49
        return func(d *ConfigDiffer) {
4✔
50
                d.filterFn = fn
2✔
51
        }
2✔
52
}
53

54
func WithOnRouteEvents(fn OnRouteEvents) Option {
11✔
55
        return func(d *ConfigDiffer) {
22✔
56
                d.onEvents = fn
11✔
57
        }
11✔
58
}
59

60
func DefaultTunnelRouteHash(p *config.Policy) uint64 {
20✔
61
        return p.Checksum()
20✔
62
}
20✔
63

64
func DefaultTunnelRouteFilter(*config.Policy) bool {
20✔
65
        return true
20✔
66
}
20✔
67

68
func NewConfigDiffer(opts ...Option) *ConfigDiffer {
13✔
69
        d := &ConfigDiffer{
13✔
70
                hashFn:   DefaultTunnelRouteHash,
13✔
71
                filterFn: DefaultTunnelRouteFilter,
13✔
72
                wakeC:    make(chan struct{}, 1),
13✔
73
                prev:     make(map[string]uint64),
13✔
74
        }
13✔
75
        for _, opt := range opts {
27✔
76
                opt(d)
14✔
77
        }
14✔
78
        return d
13✔
79
}
80

81
func (d *ConfigDiffer) OnConfigUpdated(cfg *config.Config) {
28✔
82
        d.currentConfig.Store(cfg)
28✔
83
        select {
28✔
84
        case d.wakeC <- struct{}{}:
19✔
85
        default:
9✔
86
        }
87
}
88

89
func (d *ConfigDiffer) Run(ctx context.Context) error {
12✔
90
        for {
42✔
91
                select {
30✔
92
                case <-ctx.Done():
12✔
93
                        return ctx.Err()
12✔
94
                case <-d.wakeC:
18✔
95
                        cfg := d.currentConfig.Load()
18✔
96
                        if events := d.computeDiff(cfg); len(events) > 0 && d.onEvents != nil {
34✔
97
                                d.onEvents(events)
16✔
98
                        }
16✔
99
                }
100
        }
101
}
102

103
func (d *ConfigDiffer) computeDiff(cfg *config.Config) []RouteEvent {
18✔
104
        curr := make(map[string]uint64)
18✔
105
        currPolicies := make(map[string]*config.Policy)
18✔
106

18✔
107
        if cfg != nil && cfg.Options != nil {
34✔
108
                for p := range cfg.Options.GetAllPolicies() {
40✔
109
                        if !d.filterFn(p) {
26✔
110
                                continue
2✔
111
                        }
112
                        id := p.ID
22✔
113
                        if id == "" {
22✔
NEW
114
                                continue
×
115
                        }
116
                        curr[id] = d.hashFn(p)
22✔
117
                        currPolicies[id] = p
22✔
118
                }
119
        }
120

121
        var events []RouteEvent
18✔
122

18✔
123
        for id := range d.prev {
28✔
124
                if _, ok := curr[id]; !ok {
14✔
125
                        events = append(events, RouteEvent{
4✔
126
                                Kind:    RouteDeleted,
4✔
127
                                RouteID: id,
4✔
128
                        })
4✔
129
                }
4✔
130
        }
131

132
        for id, h := range curr {
40✔
133
                oldH, existed := d.prev[id]
22✔
134
                if !existed || oldH != h {
40✔
135
                        events = append(events, RouteEvent{
18✔
136
                                Kind:    RouteUpserted,
18✔
137
                                RouteID: id,
18✔
138
                                Policy:  currPolicies[id],
18✔
139
                        })
18✔
140
                }
18✔
141
        }
142

143
        d.prev = curr
18✔
144
        return events
18✔
145
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc