store: P2R-10 schema for source-group + host-default hooks (migration 0010)
Adds pre_hook/post_hook BLOB columns to source_groups and pre_hook_default/post_hook_default to hosts. Bytes stored verbatim (AEAD encrypt/decrypt happens at the HTTP layer where the AEAD key lives). Round-trip tests cover set/clear semantics on both tables.
This commit is contained in:
@@ -45,13 +45,14 @@ func (st *Store) CreateSourceGroup(ctx context.Context, g *SourceGroup) error {
|
||||
`INSERT INTO source_groups (
|
||||
id, host_id, name, includes, excludes, retention_policy,
|
||||
retry_max, retry_backoff_seconds, conflict_dimension,
|
||||
created_at, updated_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
created_at, updated_at, pre_hook, post_hook
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
g.ID, g.HostID, g.Name,
|
||||
string(includesJSON), string(excludesJSON), string(retentionJSON),
|
||||
g.RetryMax, g.RetryBackoffSeconds,
|
||||
nullableString(g.ConflictDimension),
|
||||
now.Format(time.RFC3339Nano), now.Format(time.RFC3339Nano),
|
||||
nullableBytes(g.PreHook), nullableBytes(g.PostHook),
|
||||
); err != nil {
|
||||
return fmt.Errorf("store: create source group: %w", err)
|
||||
}
|
||||
@@ -88,13 +89,14 @@ func (st *Store) UpdateSourceGroup(ctx context.Context, g *SourceGroup) error {
|
||||
`UPDATE source_groups SET
|
||||
name = ?, includes = ?, excludes = ?, retention_policy = ?,
|
||||
retry_max = ?, retry_backoff_seconds = ?, conflict_dimension = ?,
|
||||
updated_at = ?
|
||||
updated_at = ?, pre_hook = ?, post_hook = ?
|
||||
WHERE id = ? AND host_id = ?`,
|
||||
g.Name,
|
||||
string(includesJSON), string(excludesJSON), string(retentionJSON),
|
||||
g.RetryMax, g.RetryBackoffSeconds,
|
||||
nullableString(g.ConflictDimension),
|
||||
now.Format(time.RFC3339Nano),
|
||||
nullableBytes(g.PreHook), nullableBytes(g.PostHook),
|
||||
g.ID, g.HostID,
|
||||
)
|
||||
if err != nil {
|
||||
@@ -143,7 +145,7 @@ func (st *Store) GetSourceGroup(ctx context.Context, hostID, groupID string) (*S
|
||||
row := st.db.QueryRowContext(ctx,
|
||||
`SELECT id, host_id, name, includes, excludes, retention_policy,
|
||||
retry_max, retry_backoff_seconds, conflict_dimension,
|
||||
created_at, updated_at
|
||||
created_at, updated_at, pre_hook, post_hook
|
||||
FROM source_groups WHERE id = ? AND host_id = ?`,
|
||||
groupID, hostID)
|
||||
g, err := scanSourceGroup(row)
|
||||
@@ -159,7 +161,7 @@ func (st *Store) GetSourceGroupByName(ctx context.Context, hostID, name string)
|
||||
row := st.db.QueryRowContext(ctx,
|
||||
`SELECT id, host_id, name, includes, excludes, retention_policy,
|
||||
retry_max, retry_backoff_seconds, conflict_dimension,
|
||||
created_at, updated_at
|
||||
created_at, updated_at, pre_hook, post_hook
|
||||
FROM source_groups WHERE host_id = ? AND name = ?`,
|
||||
hostID, name)
|
||||
g, err := scanSourceGroup(row)
|
||||
@@ -177,7 +179,7 @@ func (st *Store) ListSourceGroupsByHost(ctx context.Context, hostID string) ([]S
|
||||
rows, err := st.db.QueryContext(ctx,
|
||||
`SELECT id, host_id, name, includes, excludes, retention_policy,
|
||||
retry_max, retry_backoff_seconds, conflict_dimension,
|
||||
created_at, updated_at
|
||||
created_at, updated_at, pre_hook, post_hook
|
||||
FROM source_groups WHERE host_id = ? ORDER BY name`,
|
||||
hostID)
|
||||
if err != nil {
|
||||
@@ -224,14 +226,17 @@ func scanSourceGroupRow(s sourceGroupScanner) (*SourceGroup, error) {
|
||||
includes, excludes, retention string
|
||||
conflict sql.NullString
|
||||
createdAt, updatedAt string
|
||||
preHook, postHook []byte
|
||||
)
|
||||
err := s.Scan(&out.ID, &out.HostID, &out.Name,
|
||||
&includes, &excludes, &retention,
|
||||
&out.RetryMax, &out.RetryBackoffSeconds, &conflict,
|
||||
&createdAt, &updatedAt)
|
||||
&createdAt, &updatedAt, &preHook, &postHook)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out.PreHook = preHook
|
||||
out.PostHook = postHook
|
||||
if includes != "" {
|
||||
_ = json.Unmarshal([]byte(includes), &out.Includes)
|
||||
}
|
||||
@@ -259,3 +264,13 @@ func nullableString(s string) any {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// nullableBytes returns nil for an empty/nil slice so SQL stores it
|
||||
// as NULL rather than an empty BLOB. The agent treats both the same
|
||||
// (no hook), but NULL is the canonical "absent" form on disk.
|
||||
func nullableBytes(b []byte) any {
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user