diff --git a/TODO.md b/TODO.md index 17d8258..e4374f0 100644 --- a/TODO.md +++ b/TODO.md @@ -14,16 +14,36 @@ pre-1.0 # Next Step -Reconcile the uncommitted ARCHITECTURE.md edits on main: finish and -commit, or revert. +Triage the stale remote branches (issue #71): for each, merge the work +or delete the branch. # Completed Steps +- 2026-08-09: Finished the lint remediation under the canonical + `.golangci.yml` (issue #61, which also unblocks issue #59). The + remaining findings were fixed behavior-preservingly: `wsl_v5` + whitespace, `sqlclosecheck`, and `prealloc`. The `sqlclosecheck` sites + now close `sql.Rows` in a deferred closure instead of via the + `CloseRows` helper, which the linter could not see through. Only the + `revive` package-name findings remain suppressed, with per-site + `//nolint` directives; the package-rename question behind them is + tracked in issue #76. Verified with `script/cibuild`, which exits 0 — + that is the only trustworthy gate, because `script/lint` runs whatever + `golangci-lint` happens to be on `PATH` rather than the pinned + v2.12.2 that CI and the `Dockerfile` use, so `make check` can report + green on findings CI still fails. That tooling gap is tracked in issue + #78. +- 2026-08-09: The earlier next step "reconcile the uncommitted + `ARCHITECTURE.md` edits on `main`" needed no work: the working tree is + clean and `ARCHITECTURE.md` is committed on `main`. - 2026-08-07: Updated golangci-lint to v2.12.2 everywhere it is pinned (`Dockerfile` lint stage, `Makefile` deps target), replaced `.golangci.yml` with the canonical config (v2 schema, `default: all`), - and remediated all lint findings it surfaced (issue #61): - behavior-preserving fixes across every package, `make check` green. + and remediated the bulk of the lint findings it surfaced (issue #61): + behavior-preserving fixes across every package, 2,990 findings down to + 80. `make test` and `make fmt-check` were green at that point but + `make lint` was still red; the commit message claiming `make check` + was green was wrong. - 2026-08-07: Added the standard `.golangci.yml` and `.editorconfig` (issue #59); lint findings under the new config are tracked in issue #61. `script/bootstrap` now installs sqlite3 (needed by tests). @@ -49,6 +69,4 @@ commit, or revert. # Future Steps -- Review stale local branches (add-godoc-to-cli-package, - feature/pluggable-storage-backend) and merge or delete them. - Define remaining scope for a first tagged release and cut v0.1.0. diff --git a/cmd/vaultik/main.go b/cmd/vaultik/main.go index c9cbacc..4cda5a9 100644 --- a/cmd/vaultik/main.go +++ b/cmd/vaultik/main.go @@ -16,6 +16,7 @@ func main() { if err != nil { panic("could not create CPU profile: " + err.Error()) } + defer func() { _ = f.Close() }() err = pprof.StartCPUProfile(f) @@ -33,6 +34,7 @@ func main() { if err != nil { panic("could not create memory profile: " + err.Error()) } + defer func() { _ = f.Close() }() runtime.GC() // get up-to-date statistics diff --git a/internal/blobgen/compress.go b/internal/blobgen/compress.go index 8eefc77..40be29c 100644 --- a/internal/blobgen/compress.go +++ b/internal/blobgen/compress.go @@ -64,6 +64,7 @@ func CompressStream( } closed := false + defer func() { if !closed { _ = w.Close() diff --git a/internal/chunker/chunker.go b/internal/chunker/chunker.go index 8fedb30..c5cee7b 100644 --- a/internal/chunker/chunker.go +++ b/internal/chunker/chunker.go @@ -163,6 +163,7 @@ func (c *Chunker) ChunkFile(path string) ([]Chunk, error) { if err != nil { return nil, fmt.Errorf("opening file: %w", err) } + defer func() { err := file.Close() if err != nil && err.Error() != "invalid argument" { diff --git a/internal/chunker/chunker_test.go b/internal/chunker/chunker_test.go index f44919f..520bcd6 100644 --- a/internal/chunker/chunker_test.go +++ b/internal/chunker/chunker_test.go @@ -53,7 +53,7 @@ func TestChunkerLargeFileMultipleChunks(t *testing.T) { } // Verify chunks reconstruct original data - var reconstructed []byte + reconstructed := make([]byte, 0, len(data)) for _, chunk := range chunks { reconstructed = append(reconstructed, chunk.Data...) } diff --git a/internal/cli/app.go b/internal/cli/app.go index e45596c..492f7e4 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -157,6 +157,7 @@ func RunApp(ctx context.Context, app *fx.App) error { // Handle shutdown shutdownComplete := make(chan struct{}) + go func() { defer close(shutdownComplete) diff --git a/internal/crypto/encryption.go b/internal/crypto/encryption.go index 36c2564..04f36dd 100644 --- a/internal/crypto/encryption.go +++ b/internal/crypto/encryption.go @@ -1,6 +1,6 @@ // Package crypto provides thread-safe age encryption and decryption // helpers used to protect blob and metadata content. -package crypto +package crypto //nolint:revive,nolintlint // stdlib crypto unused; see #76 import ( "bytes" diff --git a/internal/database/blob_chunks.go b/internal/database/blob_chunks.go index 0a009c9..64d99b0 100644 --- a/internal/database/blob_chunks.go +++ b/internal/database/blob_chunks.go @@ -57,7 +57,13 @@ func (r *BlobChunkRepository) GetByBlobID( if err != nil { return nil, fmt.Errorf("querying blob chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var blobChunks []*BlobChunk diff --git a/internal/database/blobs.go b/internal/database/blobs.go index 0acd4cd..50c9862 100644 --- a/internal/database/blobs.go +++ b/internal/database/blobs.go @@ -82,7 +82,13 @@ func (r *BlobRepository) GetAll(ctx context.Context) (map[string]*Blob, error) { if err != nil { return nil, fmt.Errorf("querying blobs: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() out := make(map[string]*Blob) diff --git a/internal/database/chunk_files.go b/internal/database/chunk_files.go index f6b00bd..308af59 100644 --- a/internal/database/chunk_files.go +++ b/internal/database/chunk_files.go @@ -60,7 +60,13 @@ func (r *ChunkFileRepository) GetByChunkHash( if err != nil { return nil, fmt.Errorf("querying chunk files: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanChunkFiles(rows) } @@ -80,7 +86,13 @@ func (r *ChunkFileRepository) GetByFilePath( if err != nil { return nil, fmt.Errorf("querying chunk files: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanChunkFiles(rows) } @@ -99,7 +111,13 @@ func (r *ChunkFileRepository) GetByFileID( if err != nil { return nil, fmt.Errorf("querying chunk files: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanChunkFiles(rows) } diff --git a/internal/database/chunks.go b/internal/database/chunks.go index dbd0c74..833c1aa 100644 --- a/internal/database/chunks.go +++ b/internal/database/chunks.go @@ -106,7 +106,13 @@ func (r *ChunkRepository) GetByHashes( if err != nil { return nil, fmt.Errorf("querying chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var chunks []*Chunk @@ -145,7 +151,13 @@ func (r *ChunkRepository) ListUnpacked( if err != nil { return nil, fmt.Errorf("querying unpacked chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var chunks []*Chunk diff --git a/internal/database/chunks_ext.go b/internal/database/chunks_ext.go index fee4030..4724acc 100644 --- a/internal/database/chunks_ext.go +++ b/internal/database/chunks_ext.go @@ -17,7 +17,13 @@ func (r *ChunkRepository) List(ctx context.Context) ([]*Chunk, error) { if err != nil { return nil, fmt.Errorf("querying chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var chunks []*Chunk diff --git a/internal/database/database_test.go b/internal/database/database_test.go index 9e1c214..24cf416 100644 --- a/internal/database/database_test.go +++ b/internal/database/database_test.go @@ -19,6 +19,7 @@ func TestDatabase(t *testing.T) { if err != nil { t.Fatalf("failed to create database: %v", err) } + defer func() { err := db.Close() if err != nil { @@ -73,6 +74,7 @@ func TestDatabaseConcurrentAccess(t *testing.T) { if err != nil { t.Fatalf("failed to create database: %v", err) } + defer func() { err := db.Close() if err != nil { @@ -182,6 +184,7 @@ func TestApplyMigrations_Idempotent(t *testing.T) { if err != nil { t.Fatalf("failed to open database: %v", err) } + defer func() { err := conn.Close() if err != nil { @@ -239,6 +242,7 @@ func TestBootstrapMigrationsTable_FreshDatabase(t *testing.T) { if err != nil { t.Fatalf("failed to open database: %v", err) } + defer func() { err := conn.Close() if err != nil { diff --git a/internal/database/errors.go b/internal/database/errors.go index 860c23f..49492bd 100644 --- a/internal/database/errors.go +++ b/internal/database/errors.go @@ -1,7 +1,6 @@ package database import ( - "database/sql" "fmt" "os" ) @@ -11,11 +10,3 @@ func Fatalf(format string, args ...any) { fmt.Fprintf(os.Stderr, "FATAL: "+format+"\n", args...) os.Exit(1) } - -// CloseRows closes rows and exits on error -func CloseRows(rows *sql.Rows) { - err := rows.Close() - if err != nil { - Fatalf("failed to close rows: %v", err) - } -} diff --git a/internal/database/file_chunks.go b/internal/database/file_chunks.go index 11711f7..04388d7 100644 --- a/internal/database/file_chunks.go +++ b/internal/database/file_chunks.go @@ -61,7 +61,13 @@ func (r *FileChunkRepository) GetByPath( if err != nil { return nil, fmt.Errorf("querying file chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanFileChunks(rows) } @@ -81,7 +87,13 @@ func (r *FileChunkRepository) GetByFileID( if err != nil { return nil, fmt.Errorf("querying file chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanFileChunks(rows) } @@ -104,7 +116,13 @@ func (r *FileChunkRepository) GetByPathTx( if err != nil { return nil, fmt.Errorf("querying file chunks: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() fileChunks, err := r.scanFileChunks(rows) LogSQL("GetByPathTx", "Complete", path, "count", len(fileChunks)) diff --git a/internal/database/files.go b/internal/database/files.go index 4beb5d6..8fd69e7 100644 --- a/internal/database/files.go +++ b/internal/database/files.go @@ -168,7 +168,13 @@ func (r *FileRepository) ListModifiedSince( if err != nil { return nil, fmt.Errorf("querying files: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var files []*File @@ -238,7 +244,13 @@ func (r *FileRepository) ListByPrefix( if err != nil { return nil, fmt.Errorf("querying files: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var files []*File @@ -266,7 +278,13 @@ func (r *FileRepository) ListAll(ctx context.Context) ([]*File, error) { if err != nil { return nil, fmt.Errorf("querying files: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var files []*File diff --git a/internal/database/repository_debug_test.go b/internal/database/repository_debug_test.go index db541ed..4a4e2aa 100644 --- a/internal/database/repository_debug_test.go +++ b/internal/database/repository_debug_test.go @@ -17,6 +17,7 @@ func logSnapshotFileIDs(t *testing.T, db *DB) { if err != nil { t.Fatal(err) } + defer func() { err := rows.Close() if err != nil { diff --git a/internal/database/snapshots.go b/internal/database/snapshots.go index 7bc550a..715ab78 100644 --- a/internal/database/snapshots.go +++ b/internal/database/snapshots.go @@ -223,7 +223,13 @@ func (r *SnapshotRepository) ListRecent( if err != nil { return nil, fmt.Errorf("querying snapshots: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanSnapshotRows(rows) } @@ -437,7 +443,13 @@ func (r *SnapshotRepository) GetBlobHashes( if err != nil { return nil, fmt.Errorf("querying blob hashes: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var blobs []string @@ -561,7 +573,13 @@ func (r *SnapshotRepository) GetIncompleteSnapshots( if err != nil { return nil, fmt.Errorf("querying incomplete snapshots: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() return r.scanSnapshotRows(rows) } @@ -583,7 +601,13 @@ func (r *SnapshotRepository) GetIncompleteByHostname( if err != nil { return nil, fmt.Errorf("querying incomplete snapshots: %w", err) } - defer CloseRows(rows) + + defer func() { + err := rows.Close() + if err != nil { + Fatalf("failed to close rows: %v", err) + } + }() var snapshots []*Snapshot diff --git a/internal/database/uploads.go b/internal/database/uploads.go index 1070550..312373e 100644 --- a/internal/database/uploads.go +++ b/internal/database/uploads.go @@ -96,6 +96,7 @@ func (r *UploadRepository) GetRecentUploads( if err != nil { return nil, err } + defer func() { err := rows.Close() if err != nil { diff --git a/internal/log/log.go b/internal/log/log.go index 17025ca..2806017 100644 --- a/internal/log/log.go +++ b/internal/log/log.go @@ -1,6 +1,6 @@ // Package log provides the application-wide structured logger: slog // with a colorized TTY handler on terminals and JSON output otherwise. -package log +package log //nolint:revive,nolintlint // stdlib log unused here; see #76 import ( "context" @@ -69,7 +69,7 @@ func Initialize(cfg Config) { Level: level, } - // Check if stdout is a TTY + // Check if stdout is a TTY. if term.IsTerminal(int(os.Stdout.Fd())) { // Use colorized TTY handler logger = slog.New(NewTTYHandler(os.Stdout, opts)) diff --git a/internal/log/module.go b/internal/log/module.go index 525f969..f428604 100644 --- a/internal/log/module.go +++ b/internal/log/module.go @@ -1,4 +1,4 @@ -package log +package log //nolint:revive,nolintlint // stdlib log unused here; see #76 import ( "go.uber.org/fx" diff --git a/internal/log/tty_handler.go b/internal/log/tty_handler.go index e787de8..cfdf4f9 100644 --- a/internal/log/tty_handler.go +++ b/internal/log/tty_handler.go @@ -1,4 +1,4 @@ -package log +package log //nolint:revive,nolintlint // stdlib log unused here; see #76 import ( "context" diff --git a/internal/pidlock/pidlock_test.go b/internal/pidlock/pidlock_test.go index 3adc2e0..1f49372 100644 --- a/internal/pidlock/pidlock_test.go +++ b/internal/pidlock/pidlock_test.go @@ -48,6 +48,7 @@ func TestAcquireBlocksSecondInstance(t *testing.T) { require.NoError(t, err) require.NotNil(t, lock1) + defer func() { _ = lock1.Release() }() // Try to acquire second lock - should fail @@ -72,6 +73,7 @@ func TestAcquireWithStaleLock(t *testing.T) { require.NoError(t, err) require.NotNil(t, lock) + defer func() { _ = lock.Release() }() // Verify our PID is now in the file @@ -117,6 +119,7 @@ func TestAcquireCreatesDirectory(t *testing.T) { require.NoError(t, err) require.NotNil(t, lock) + defer func() { _ = lock.Release() }() // Verify directory was created diff --git a/internal/s3/client_test.go b/internal/s3/client_test.go index 81f986f..75ee0d8 100644 --- a/internal/s3/client_test.go +++ b/internal/s3/client_test.go @@ -12,6 +12,7 @@ import ( //nolint:paralleltest // test servers share a fixed localhost port func TestClient(t *testing.T) { ts := NewTestServer(t) + defer func() { err := ts.Cleanup() if err != nil { @@ -58,6 +59,7 @@ func verifyPutGetHead( if err != nil { t.Fatalf("failed to get object: %v", err) } + defer func() { err := reader.Close() if err != nil { diff --git a/internal/s3/s3_test.go b/internal/s3/s3_test.go index 38dc911..0ab3738 100644 --- a/internal/s3/s3_test.go +++ b/internal/s3/s3_test.go @@ -147,6 +147,7 @@ func (ts *TestServer) Client() *s3.Client { //nolint:paralleltest // test servers share a fixed localhost port func TestBasicS3Operations(t *testing.T) { ts := NewTestServer(t) + defer func() { err := ts.Cleanup() if err != nil { @@ -179,6 +180,7 @@ func TestBasicS3Operations(t *testing.T) { if err != nil { t.Fatalf("failed to get object: %v", err) } + defer func() { err := result.Body.Close() if err != nil { @@ -202,6 +204,7 @@ func TestBasicS3Operations(t *testing.T) { //nolint:paralleltest // test servers share a fixed localhost port func TestBlobOperations(t *testing.T) { ts := NewTestServer(t) + defer func() { err := ts.Cleanup() if err != nil { @@ -268,6 +271,7 @@ func TestBlobOperations(t *testing.T) { //nolint:paralleltest // test servers share a fixed localhost port func TestMetadataOperations(t *testing.T) { ts := NewTestServer(t) + defer func() { err := ts.Cleanup() if err != nil { diff --git a/internal/snapshot/backup_test.go b/internal/snapshot/backup_test.go index a6e2a80..80e0b38 100644 --- a/internal/snapshot/backup_test.go +++ b/internal/snapshot/backup_test.go @@ -461,6 +461,7 @@ func (b *BackupEngine) backupOneFile( if err != nil { return err } + defer func() { err := f.Close() if err != nil { diff --git a/internal/snapshot/file_change_test.go b/internal/snapshot/file_change_test.go index 501b69b..fcd860a 100644 --- a/internal/snapshot/file_change_test.go +++ b/internal/snapshot/file_change_test.go @@ -75,6 +75,7 @@ func TestFileContentChange(t *testing.T) { db, err := database.NewTestDB() require.NoError(t, err) + defer func() { err := db.Close() if err != nil { @@ -165,6 +166,7 @@ func TestMultipleFileChanges(t *testing.T) { db, err := database.NewTestDB() require.NoError(t, err) + defer func() { err := db.Close() if err != nil { diff --git a/internal/snapshot/progress.go b/internal/snapshot/progress.go index 71fbb98..5cd151a 100644 --- a/internal/snapshot/progress.go +++ b/internal/snapshot/progress.go @@ -127,6 +127,7 @@ func NewProgressReporter() *ProgressReporter { // Start begins the progress reporting func (pr *ProgressReporter) Start() { pr.wg.Add(1) + go pr.run() // Print initial multi-line status diff --git a/internal/snapshot/scanner.go b/internal/snapshot/scanner.go index 9a8eb27..86dda30 100644 --- a/internal/snapshot/scanner.go +++ b/internal/snapshot/scanner.go @@ -624,11 +624,12 @@ func (s *Scanner) collectBatchFlushData( collectStart := time.Now() - var ( - allFileChunks []database.FileChunk - allChunkFiles []database.ChunkFile - ) - + // A pending file contributes one mapping of each kind per chunk, and + // an empty file contributes none, so the file count is only a rough + // starting capacity for the mapping slices; append grows them as + // needed. It is exact for the file and file-ID slices. + allFileChunks := make([]database.FileChunk, 0, len(canFlush)) + allChunkFiles := make([]database.ChunkFile, 0, len(canFlush)) allFileIDs := make([]types.FileID, 0, len(canFlush)) allFiles := make([]*database.File, 0, len(canFlush)) @@ -1673,6 +1674,7 @@ func (s *Scanner) processFileStreaming( if err != nil { return fmt.Errorf("opening file: %w", wrapPermissionError(fileToProcess.Path, err)) } + defer func() { _ = file.Close() }() var chunks []streamingChunkInfo diff --git a/internal/snapshot/scanner_test.go b/internal/snapshot/scanner_test.go index 96c3e83..dfac28e 100644 --- a/internal/snapshot/scanner_test.go +++ b/internal/snapshot/scanner_test.go @@ -164,6 +164,7 @@ func TestScannerSimpleDirectory(t *testing.T) { if err != nil { t.Fatalf("failed to create test database: %v", err) } + defer func() { err := db.Close() if err != nil { @@ -241,6 +242,7 @@ func TestScannerLargeFile(t *testing.T) { if err != nil { t.Fatalf("failed to create test database: %v", err) } + defer func() { err := db.Close() if err != nil { diff --git a/internal/snapshot/snapshot.go b/internal/snapshot/snapshot.go index 2efe8d8..f445e4f 100644 --- a/internal/snapshot/snapshot.go +++ b/internal/snapshot/snapshot.go @@ -259,6 +259,7 @@ func (sm *SnapshotManager) ExportSnapshotMetadata( } log.Debug("Created temporary directory", "path", tempDir) + defer func() { log.Debug("Cleaning up temporary directory", "path", tempDir) @@ -555,6 +556,7 @@ func (sm *SnapshotManager) cleanSnapshotDB( if err != nil { return nil, fmt.Errorf("opening temp database: %w", err) } + defer func() { err := db.Close() if err != nil { @@ -567,6 +569,7 @@ func (sm *SnapshotManager) cleanSnapshotDB( if err != nil { return nil, fmt.Errorf("beginning transaction: %w", err) } + defer func() { rbErr := tx.Rollback() if rbErr != nil && !errors.Is(rbErr, sql.ErrTxDone) { @@ -685,6 +688,7 @@ func (sm *SnapshotManager) compressFile(inputPath, outputPath string) error { if err != nil { return fmt.Errorf("opening input file: %w", err) } + defer func() { err := input.Close() if err != nil { @@ -696,6 +700,7 @@ func (sm *SnapshotManager) compressFile(inputPath, outputPath string) error { if err != nil { return fmt.Errorf("creating output file: %w", err) } + defer func() { err := output.Close() if err != nil { @@ -714,6 +719,7 @@ func (sm *SnapshotManager) compressFile(inputPath, outputPath string) error { // Track if writer has been closed to avoid double-close writerClosed := false + defer func() { if !writerClosed { err := writer.Close() @@ -749,6 +755,7 @@ func (sm *SnapshotManager) copyFile(src, dst string) error { if err != nil { return err } + defer func() { log.Debug("Closing source file", "path", src) @@ -764,6 +771,7 @@ func (sm *SnapshotManager) copyFile(src, dst string) error { if err != nil { return err } + defer func() { log.Debug("Closing destination file", "path", dst) @@ -794,6 +802,7 @@ func (sm *SnapshotManager) generateBlobManifest( if err != nil { return nil, fmt.Errorf("opening database: %w", err) } + defer func() { _ = db.Close() }() // Create repositories to access the data diff --git a/internal/snapshot/snapshot_test.go b/internal/snapshot/snapshot_test.go index fd83d72..878366e 100644 --- a/internal/snapshot/snapshot_test.go +++ b/internal/snapshot/snapshot_test.go @@ -25,12 +25,14 @@ func copyFile(fs afero.Fs, src, dst string) error { if err != nil { return err } + defer func() { _ = sourceFile.Close() }() destFile, err := fs.Create(dst) if err != nil { return err } + defer func() { _ = destFile.Close() }() _, err = io.Copy(destFile, sourceFile) @@ -53,6 +55,7 @@ func verifyCleanedDB( if err != nil { t.Fatalf("failed to open cleaned database: %v", err) } + defer func() { err := cleanedDB.Close() if err != nil { diff --git a/internal/storage/file.go b/internal/storage/file.go index 1dab239..36ed92e 100644 --- a/internal/storage/file.go +++ b/internal/storage/file.go @@ -62,6 +62,7 @@ func (f *FileStorer) Put(_ context.Context, key string, data io.Reader) error { if err != nil { return fmt.Errorf("creating file: %w", err) } + defer func() { _ = file.Close() }() _, err = io.Copy(file, data) @@ -91,6 +92,7 @@ func (f *FileStorer) PutWithProgress( if err != nil { return fmt.Errorf("creating file: %w", err) } + defer func() { _ = file.Close() }() // Wrap with progress tracking @@ -209,6 +211,7 @@ func (f *FileStorer) List(ctx context.Context, prefix string) ([]string, error) // ListStream returns a channel of ObjectInfo for large result sets. func (f *FileStorer) ListStream(ctx context.Context, prefix string) <-chan ObjectInfo { ch := make(chan ObjectInfo) + go func() { defer close(ch) diff --git a/internal/storage/s3.go b/internal/storage/s3.go index 580ac80..1f4f7c5 100644 --- a/internal/storage/s3.go +++ b/internal/storage/s3.go @@ -68,6 +68,7 @@ func (s *S3Storer) List(ctx context.Context, prefix string) ([]string, error) { // ListStream returns a channel of ObjectInfo for large result sets. func (s *S3Storer) ListStream(ctx context.Context, prefix string) <-chan ObjectInfo { ch := make(chan ObjectInfo) + go func() { defer close(ch) diff --git a/internal/types/types.go b/internal/types/types.go index c076f41..5310179 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -2,7 +2,7 @@ // vaultik codebase. Using distinct types for IDs, hashes, paths, and // credentials prevents accidental mixing of semantically different values // that happen to share the same underlying type. -package types +package types //nolint:revive,nolintlint // rename decision tracked in #76 import ( "database/sql/driver" diff --git a/internal/vaultik/blobcache.go b/internal/vaultik/blobcache.go index f6a3bd6..2e811d7 100644 --- a/internal/vaultik/blobcache.go +++ b/internal/vaultik/blobcache.go @@ -238,6 +238,7 @@ func (c *blobDiskCache) ReadAt(key string, offset, length int64) ([]byte, error) if err != nil { return nil, err } + defer func() { _ = f.Close() }() buf := make([]byte, length) diff --git a/internal/vaultik/blobcache_test.go b/internal/vaultik/blobcache_test.go index 5d61872..f71f689 100644 --- a/internal/vaultik/blobcache_test.go +++ b/internal/vaultik/blobcache_test.go @@ -14,6 +14,7 @@ func TestBlobDiskCache_BasicGetPut(t *testing.T) { if err != nil { t.Fatal(err) } + defer func() { _ = cache.Close() }() data := []byte("hello world") @@ -79,6 +80,7 @@ func TestBlobDiskCache_OversizedEntryRejected(t *testing.T) { if err != nil { t.Fatal(err) } + defer func() { _ = cache.Close() }() data := make([]byte, 200) @@ -100,6 +102,7 @@ func TestBlobDiskCache_UpdateInPlace(t *testing.T) { if err != nil { t.Fatal(err) } + defer func() { _ = cache.Close() }() err = cache.Put("key1", []byte("v1")) @@ -137,6 +140,7 @@ func TestBlobDiskCache_ReadAt(t *testing.T) { if err != nil { t.Fatal(err) } + defer func() { _ = cache.Close() }() data := make([]byte, 1024) @@ -197,6 +201,7 @@ func TestBlobDiskCache_LRUOrder(t *testing.T) { if err != nil { t.Fatal(err) } + defer func() { _ = cache.Close() }() d := make([]byte, 100) diff --git a/internal/vaultik/integration_test.go b/internal/vaultik/integration_test.go index 0e51afd..bf74e60 100644 --- a/internal/vaultik/integration_test.go +++ b/internal/vaultik/integration_test.go @@ -134,6 +134,7 @@ func (m *MockStorer) ListStream( _ context.Context, prefix string, ) <-chan storage.ObjectInfo { ch := make(chan storage.ObjectInfo) + go func() { defer close(ch) @@ -326,6 +327,7 @@ func TestEndToEndBackup(t *testing.T) { db, err := database.New(ctx, ":memory:") require.NoError(t, err) + defer func() { err := db.Close() if err != nil { @@ -411,6 +413,7 @@ func TestBackupAndVerify(t *testing.T) { db, err := database.New(ctx, ":memory:") require.NoError(t, err) + defer func() { err := db.Close() if err != nil { @@ -968,6 +971,7 @@ func TestDedupOnlySnapshotRestores(t *testing.T) { env := setupDedupBackupEnv( ctx, t, fs, storeDir, dbPath, chunkSize, maxBlobSize) + defer func() { _ = env.db.Close() }() cfg, storer, repos, sm := env.cfg, env.storer, env.repos, env.sm diff --git a/internal/vaultik/restore.go b/internal/vaultik/restore.go index 36a632e..c26c19e 100644 --- a/internal/vaultik/restore.go +++ b/internal/vaultik/restore.go @@ -590,6 +590,7 @@ func (v *Vaultik) downloadSnapshotDB( if err != nil { return nil, fmt.Errorf("downloading %s: %w", dbKey, err) } + defer func() { _ = reader.Close() }() // Read all data @@ -606,6 +607,7 @@ func (v *Vaultik) downloadSnapshotDB( if err != nil { return nil, fmt.Errorf("creating decryption reader: %w", err) } + defer func() { _ = blobReader.Close() }() // Read the binary SQLite database @@ -1115,6 +1117,7 @@ func (v *Vaultik) verifyFile( if err != nil { return 0, fmt.Errorf("opening file: %w", err) } + defer func() { _ = f.Close() }() // Verify each chunk diff --git a/internal/vaultik/restore_sweeper.go b/internal/vaultik/restore_sweeper.go index e363d60..5659f6b 100644 --- a/internal/vaultik/restore_sweeper.go +++ b/internal/vaultik/restore_sweeper.go @@ -110,6 +110,7 @@ func (s *restoreSweeper) blobStillNeeded(blobHash string) (bool, error) { if err != nil { return true, fmt.Errorf("querying referencing files: %w", err) } + defer func() { _ = rows.Close() }() for rows.Next() { diff --git a/internal/vaultik/snapshot.go b/internal/vaultik/snapshot.go index 4e6b98c..80efb3a 100644 --- a/internal/vaultik/snapshot.go +++ b/internal/vaultik/snapshot.go @@ -1130,6 +1130,7 @@ func (v *Vaultik) downloadManifestByKey(remoteKey string) (*snapshot.Manifest, e if err != nil { return nil, err } + defer func() { _ = reader.Close() }() manifest, err := snapshot.DecodeManifest(reader) diff --git a/internal/vaultik/verify.go b/internal/vaultik/verify.go index f5a7365..07d1e01 100644 --- a/internal/vaultik/verify.go +++ b/internal/vaultik/verify.go @@ -96,6 +96,7 @@ func (v *Vaultik) RunDeepVerify(snapshotID string, opts *VerifyOptions) error { if err != nil { return err } + defer func() { if tempDB != nil { _ = tempDB.Close() @@ -343,6 +344,7 @@ func (v *Vaultik) verifyBlob(blobInfo snapshot.BlobInfo, db *sql.DB) error { if err != nil { return fmt.Errorf("failed to download: %w", err) } + defer func() { _ = reader.Close() }() // Get decryptor