feat: add resumable file archive jobs and offline verification
This commit is contained in:
@@ -120,6 +120,32 @@ type archiveData struct {
|
||||
}
|
||||
|
||||
func (s *RetentionPolicyStore) ExportRetention(ctx context.Context, t auth.Token, in retention.ArchiveInput) (retention.ArchiveSnapshot, error) {
|
||||
return s.exportRetention(ctx, t.NamespaceID, in, func(ctx context.Context, tx *sql.Tx) (string, error) {
|
||||
actor, err := retentionActor(ctx, tx, t)
|
||||
return "console:" + string(actor), err
|
||||
})
|
||||
}
|
||||
|
||||
// ExportRetentionForJob 仅供持有数据库管理权限的归档 CLI 使用,不注册为 HTTP 路由。
|
||||
// 每批核验应用/命名空间启用状态和数据库策略版本;作业标识进入审计,不能冒充控制台用户。
|
||||
func (s *RetentionPolicyStore) ExportRetentionForJob(ctx context.Context, namespace, job core.ID, in retention.ArchiveInput) (retention.ArchiveSnapshot, error) {
|
||||
if namespace.Validate() != nil || job.Validate() != nil {
|
||||
return retention.ArchiveSnapshot{}, fault.Invalid("invalid archive job identity")
|
||||
}
|
||||
return s.exportRetention(ctx, namespace, in, func(ctx context.Context, tx *sql.Tx) (string, error) {
|
||||
var enabled bool
|
||||
err := tx.QueryRowContext(ctx, `SELECT a.enabled AND n.enabled FROM orbit_namespaces n JOIN orbit_applications a ON a.id=n.application_id WHERE n.id=? FOR SHARE`, namespace).Scan(&enabled)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !enabled {
|
||||
return "", fault.Forbidden
|
||||
}
|
||||
return "archive:" + string(job), nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *RetentionPolicyStore) exportRetention(ctx context.Context, namespace core.ID, in retention.ArchiveInput, authorize func(context.Context, *sql.Tx) (string, error)) (retention.ArchiveSnapshot, error) {
|
||||
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
||||
defer cancel()
|
||||
if in.Limit == 0 {
|
||||
@@ -134,11 +160,11 @@ func (s *RetentionPolicyStore) ExportRetention(ctx context.Context, t auth.Token
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
actor, err := retentionActor(ctx, tx, t)
|
||||
actor, err := authorize(ctx, tx)
|
||||
if err != nil {
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
p, err := s.readPolicy(ctx, tx, t.NamespaceID, true)
|
||||
p, err := s.readPolicy(ctx, tx, namespace, true)
|
||||
if err != nil {
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
@@ -166,13 +192,13 @@ func (s *RetentionPolicyStore) ExportRetention(ctx context.Context, t auth.Token
|
||||
}
|
||||
b, e := base64.RawURLEncoding.DecodeString(in.Cursor)
|
||||
var c archiveCursor
|
||||
if e != nil || json.Unmarshal(b, &c) != nil || c.Namespace != t.NamespaceID || c.Kind != in.Kind || c.Revision != p.Revision || c.ID.Validate() != nil || c.Cutoff.After(cutoff) || c.Time.IsZero() || !c.Time.Before(c.Cutoff) {
|
||||
if e != nil || json.Unmarshal(b, &c) != nil || c.Namespace != namespace || c.Kind != in.Kind || c.Revision != p.Revision || c.ID.Validate() != nil || c.Cutoff.After(cutoff) || c.Time.IsZero() || !c.Time.Before(c.Cutoff) {
|
||||
return retention.ArchiveSnapshot{}, fault.Invalid("invalid archive cursor")
|
||||
}
|
||||
cursor = &c
|
||||
cutoff = c.Cutoff
|
||||
}
|
||||
roots, err := archiveCandidates(ctx, tx, t.NamespaceID, source, cutoff, in.Limit+1, cursor)
|
||||
roots, err := archiveCandidates(ctx, tx, namespace, source, cutoff, in.Limit+1, cursor)
|
||||
if err != nil {
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
@@ -180,7 +206,7 @@ func (s *RetentionPolicyStore) ExportRetention(ctx context.Context, t auth.Token
|
||||
if len(roots) > in.Limit {
|
||||
roots = roots[:in.Limit]
|
||||
last := roots[len(roots)-1]
|
||||
raw, _ := json.Marshal(archiveCursor{t.NamespaceID, in.Kind, p.Revision, cutoff, last.at, last.id})
|
||||
raw, _ := json.Marshal(archiveCursor{namespace, in.Kind, p.Revision, cutoff, last.at, last.id})
|
||||
data.NextCursor = base64.RawURLEncoding.EncodeToString(raw)
|
||||
}
|
||||
data.RootCount = len(roots)
|
||||
@@ -206,10 +232,10 @@ func (s *RetentionPolicyStore) ExportRetention(ctx context.Context, t auth.Token
|
||||
}
|
||||
}
|
||||
// 流程固定版本与全部历史关联任务也一起导出,不能只保存实例头。
|
||||
if err := archiveRows(ctx, tx, &data, &budget, "weave_definition_versions", "id IN (SELECT definition_version_id FROM weave_instances WHERE namespace_id=? AND id=?)", t.NamespaceID, root.id); err != nil {
|
||||
if err := archiveRows(ctx, tx, &data, &budget, "weave_definition_versions", "id IN (SELECT definition_version_id FROM weave_instances WHERE namespace_id=? AND id=?)", namespace, root.id); err != nil {
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
taskRows, err := tx.QueryContext(ctx, `SELECT id FROM pulse_tasks WHERE namespace_id=? AND gate_id=(SELECT gate_id FROM weave_instances WHERE namespace_id=? AND id=?) LIMIT 10001`, t.NamespaceID, t.NamespaceID, root.id)
|
||||
taskRows, err := tx.QueryContext(ctx, `SELECT id FROM pulse_tasks WHERE namespace_id=? AND gate_id=(SELECT gate_id FROM weave_instances WHERE namespace_id=? AND id=?) LIMIT 10001`, namespace, namespace, root.id)
|
||||
if err != nil {
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
@@ -265,7 +291,7 @@ func (s *RetentionPolicyStore) ExportRetention(ctx context.Context, t auth.Token
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
info, _ := json.Marshal(map[string]any{"kind": in.Kind, "policy_revision": p.Revision, "root_count": len(roots), "rows": budget.rows, "sha256": digest, "source_retained": true})
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO orbit_audit_events(namespace_id,id,operation_id,actor_id,action,resource_kind,resource_id,reason,`after`) VALUES (?,?,?,?,'retention.archive.export','retention_policy',?,'export retained history snapshot',?)", t.NamespaceID, audit, op, "console:"+string(actor), t.NamespaceID, string(info))
|
||||
_, err = tx.ExecContext(ctx, "INSERT INTO orbit_audit_events(namespace_id,id,operation_id,actor_id,action,resource_kind,resource_id,reason,`after`) VALUES (?,?,?,?,'retention.archive.export','retention_policy',?,'export retained history snapshot',?)", namespace, audit, op, actor, namespace, string(info))
|
||||
if err != nil {
|
||||
return retention.ArchiveSnapshot{}, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user