Conversation
Deploying infrahub-ops-cli with
|
| Latest commit: |
37a4ef0
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0843f101.infrahub-ops-cli.pages.dev |
| Branch Preview URL: | https://002-plakar-integration.infrahub-ops-cli.pages.dev |
BeArchiTek
left a comment
There was a problem hiding this comment.
Detailed review comments on the Plakar integration architecture and key design decisions.
| } | ||
|
|
||
| // Scan returns a channel with a root directory record and a single file record. | ||
| func (imp *StreamingImporter) Scan(_ context.Context) (<-chan *importer.ScanResult, error) { |
There was a problem hiding this comment.
Streaming Pipeline: The StreamingImporter implements kloset's importer.Importer interface using a lazy dataFunc factory. When kloset calls Scan(), it receives a channel with a root directory record and a single file record. The dataFunc is only invoked when kloset reads the file — this is when the container exec pipe is actually established, avoiding any upfront data buffering.
| } | ||
|
|
||
| // openOrCreateRepo opens an existing Plakar repository, or creates a new one if it doesn't exist. | ||
| func openOrCreateRepo(kctx *kcontext.KContext, cfg *PlakarConfig) (*repository.Repository, error) { |
There was a problem hiding this comment.
Repository Lifecycle: openOrCreateRepo tries storage.Open() first. If it fails (repo doesn't exist), it creates a new repository with plaintext config (no encryption), closes it, then re-opens to get the proper config bytes for repository.New(). The create-close-reopen pattern is required by kloset's storage layer.
| } | ||
|
|
||
| // Generate backup-id timestamp | ||
| backupID := time.Now().Format("20060102_150405") |
There was a problem hiding this comment.
Backup Group Model: Each CreatePlakarBackup call generates a timestamp-based backupID and creates one snapshot per component (neo4j, postgres, metadata). All snapshots in a group share the same infrahub.backup-id tag, which is used at query time to reconstruct groups. Partial failure tracking logs completed components but does not attempt rollback — incomplete groups are flagged at list/restore time.
| } | ||
|
|
||
| // restoreBackupGroup exports each component snapshot to a temp directory and restores. | ||
| func (iops *InfrahubOps) restoreBackupGroup(kctx *kcontext.KContext, repo *repository.Repository, group *BackupGroupInfo, excludeTaskManager bool, restoreMigrateFormat bool) error { |
There was a problem hiding this comment.
Restore Restructuring: The restore extracts each component snapshot to its own subdirectory (backup/neo4j/, backup/postgres/, backup/metadata/), then renames directories to match the layout expected by the existing restoreNeo4j() and restorePostgreSQL() functions. This avoids duplicating any restore logic.
| } | ||
|
|
||
| // resolveSnapshotID resolves a snapshot identifier (partial hex or empty for latest). | ||
| func resolveSnapshotID(repo snapshotLister, snapshotID string) (objects.MAC, error) { |
There was a problem hiding this comment.
Snapshot ID Resolution: resolveSnapshotID supports hex prefix matching — users can provide partial IDs (e.g., a3f2b1c8) and the function finds the matching snapshot. Ambiguous prefixes (matching multiple snapshots) return a clear error asking for a longer prefix. Empty ID returns the latest snapshot.
| return groups, nil | ||
| } | ||
|
|
||
| // determineGroupStatus checks if all expected components are present. |
There was a problem hiding this comment.
Group Status Derivation: Completeness is not stored — it's computed at query time by comparing present component snapshots against the infrahub.components tag. This means kloset's immutable snapshot model works naturally: no need to update tags after creation. If a backup fails midway, the group simply has fewer snapshots than expected.
| ) | ||
|
|
||
| // validateBackendFlags checks for invalid flag combinations related to the --backend flag. | ||
| func validateBackendFlags(iops *app.InfrahubOps) error { |
There was a problem hiding this comment.
Backend Validation: S3 flags (--s3-upload, --s3-bucket, etc.) are explicitly rejected when using plakar backend. For S3 with plakar, users should use --repo s3://bucket/prefix instead, which routes through kloset's integration-s3 storage backend.
cf793d2 to
9b54c5f
Compare
- Fix CLAUDE.md markdown lint errors (blank lines around headings/lists) - Deduplicate Active Technologies entries in CLAUDE.md - Update vendorHash in flake.nix after go.mod dependency changes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Adds Plakar (kloset) as an optional backup backend, enabling content-addressed, deduplicated backups alongside the existing tar.gz workflow.
--backend plakarand--repoflags forcreateandrestorecommandssnapshots listcommand to browse available backupsArchitecture
Component Design
Each Plakar backup creates 3 snapshots grouped by a timestamp-based
backup-id:neo4j/neo4j-backup.tar(enterprise) or/neo4j.dump(community)neo4j-admin database backup/dumpstreamed from containerpostgres/prefect.dumppg_dumpstreamed from task-manager-db containermetadata/backup_information.jsonSnapshot Tags
All metadata is stored as kloset snapshot tags for query-time filtering:
Streaming Pipeline
Data flows directly from container exec → kloset chunker → repository, avoiding local temp files:
StreamingImporterimplementsimporter.Importerwith a lazydataFuncfactorydataFunccallsCommandExecutor.ExecStreamStdout()which returns anio.ReadCloserpipe fromdocker compose execsnapshot.Backup()reads from the pipe, content-addresses chunks, and deduplicates against existing dataBackup Group Integrity
infrahub.componentstagincompletestatusrestorerefuses incomplete groups unless--forceis usedRestore Flow
Restore extracts snapshots to a temp directory, then restructures files to match the existing restore functions' expected layout:
This reuses all existing
restoreNeo4j()andrestorePostgreSQL()logic without modification.New Files
src/internal/app/plakar.gosrc/internal/app/plakar_backup.goCreatePlakarBackup()— orchestrates streaming backupsrc/internal/app/plakar_restore.goRestorePlakarBackup()— group restore, single snapshot restore, snapshot ID resolutionsrc/internal/app/snapshots.goListSnapshots(), backup group collection, status determination, tag parsingsrc/internal/app/importer.goStreamingImporter— kloset importer for pipe-based dataModified Files
src/cmd/infrahub-backup/main.go--backend,--repo,--backup-id,--snapshotflags;snapshots listcommand; backend validationsrc/internal/app/app.goBackendType,PlakarConfig, routing inCreateBackup()/RestoreBackup()src/internal/app/cli.go--backend,--repo,--backup-id,--snapshotto shared flag configurationsrc/internal/app/backup_neo4j.gobackupNeo4jEnterpriseStream()andbackupNeo4jCommunityStream()for pipe-based dumpssrc/internal/app/backup_taskmanager.gobackupTaskManagerDBStream()for pipe-based pg_dumpsrc/internal/app/command_executor.goExecStreamStdout()for streaming container execUsage
Create a backup
List available backups
Restore from a backup
Default behavior (unchanged)
# Still works exactly as before — produces tar.gz infrahub-backup create infrahub-backup restore backup-20260318.tar.gzDependencies Added
github.com/PlakarKorp/klosetv1.0.13 — Core content-addressed storage librarygithub.com/PlakarKorp/integration-fs— Filesystem storage/importer/exporter backendgithub.com/PlakarKorp/integration-s3— S3 storage backendTest plan
create --backend plakarproduces snapshots in a local reposnapshots listdisplays grouped backup info correctlyrestore --backend plakarrestores from latest complete snapshot grouprestore --backup-idtargets a specific backup grouprestore --snapshotrestores a single componentrestore --forceallows restoring incomplete groups--backend plakarwithout--reporeturns a clear error--s3-uploadconflicts)🤖 Generated with Claude Code