Add central ASN address-group expander
Expand asn address groups to concrete prefixes centrally (one iplocate key, consistent fleet-wide) and refresh them on a per-group TTL (default 24h). A background Refresher scans for due groups, unions each group's ASNs to a deduped prefix set, and writes them to a new resolved/resolved_at column (migration 0002). Fail-safe: a lookup error or empty expansion keeps the last-good set, never emptying it. The compiler folds resolved prefixes into the rendered set members; membership churn bumps the generation but never rewrites rules. The iplocate client is endpoint-configurable and response-tolerant, documented as needing endpoint/key confirmation. Unit tests cover TTL parsing, due-checks, and union/dedup/error propagation with a fake expander.
This commit is contained in:
+29
-3
@@ -158,7 +158,8 @@ func (s *Store) UpsertZone(ctx context.Context, z model.Zone) error {
|
||||
|
||||
func (s *Store) ListAddressGroups(ctx context.Context) ([]model.AddressGroup, error) {
|
||||
rows, err := s.pool.Query(ctx,
|
||||
`SELECT name, type, members, refresh, description FROM address_groups ORDER BY name`)
|
||||
`SELECT name, type, members, refresh, description, resolved, resolved_at
|
||||
FROM address_groups ORDER BY name`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -166,18 +167,43 @@ func (s *Store) ListAddressGroups(ctx context.Context) ([]model.AddressGroup, er
|
||||
var out []model.AddressGroup
|
||||
for rows.Next() {
|
||||
var g model.AddressGroup
|
||||
var members []byte
|
||||
if err := rows.Scan(&g.Name, &g.Type, &members, &g.Refresh, &g.Description); err != nil {
|
||||
var members, resolved []byte
|
||||
if err := rows.Scan(&g.Name, &g.Type, &members, &g.Refresh, &g.Description, &resolved, &g.ResolvedAt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(members, &g.Members); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := json.Unmarshal(resolved, &g.Resolved); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out = append(out, g)
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
// UpdateResolvedPrefixes stores expanded ASN prefixes for a group and bumps the
|
||||
// generation so agents re-pull. Membership churn is stored separately from the
|
||||
// group definition, so an UpsertAddressGroup never clobbers it.
|
||||
func (s *Store) UpdateResolvedPrefixes(ctx context.Context, name string, prefixes []string) error {
|
||||
resolved, err := jsonb(prefixes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return pgx.BeginFunc(ctx, s.pool, func(tx pgx.Tx) error {
|
||||
tag, err := tx.Exec(ctx,
|
||||
`UPDATE address_groups SET resolved = $2, resolved_at = now() WHERE name = $1 AND type = 'asn'`,
|
||||
name, resolved)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if tag.RowsAffected() == 0 {
|
||||
return ErrNotFound
|
||||
}
|
||||
return bump(ctx, tx)
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Store) UpsertAddressGroup(ctx context.Context, g model.AddressGroup) error {
|
||||
members, err := jsonb(g.Members)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user