Remediate all lint findings under the canonical golangci-lint config
Fix every finding surfaced by the canonical .golangci.yml with golangci-lint v2.12.2 (refs #61), behavior-preserving throughout: - err113: dynamic errors replaced with package-level sentinels and %w wrapping; direct comparisons converted to errors.Is - goprintffuncname: printf-style helpers renamed with an f suffix (ui.Writer message methods, cli.ReportErrorf, database.Fatalf, vaultik stdoutf) and all call sites updated - revive: stuttering type names renamed (blob.Handler, blob.WithReader, blob.ChunkPosition, storage.URL, storage.Info), doc comments added, unused parameters blanked, package comments added - contextcheck/noctx: ctx threaded through blob.Packer (AddChunk/Flush/FinalizeBlob/PackChunks) and scanner call sites; context-aware exec and sql variants used - funlen/cyclop/gocognit/nestif/dupl: oversized or duplicated functions split into focused helpers across production and test code - paralleltest/tparallel/thelper/usetesting/testpackage: tests parallelized where safe (global log.Initialize kept in the serial phase), helpers marked, t.TempDir adopted, external test packages where only exported API is used - gosec: integer conversions clamped or justified, header timeouts added, remaining findings suppressed with per-site justifications - mnd/goconst/lll/wsl_v5/nlreturn/noinlineerr/errcheck and other mechanical findings fixed directly Remove the deprecated log.LogOptions alias (callers migrated to log.Options). make check is green.
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
// Package s3 wraps the AWS S3 SDK with a simplified client for vaultik's
|
||||
// bucket-and-prefix scoped object operations.
|
||||
package s3
|
||||
|
||||
import (
|
||||
@@ -42,7 +44,7 @@ type Config struct {
|
||||
// Used to suppress SDK warnings about checksums.
|
||||
type nopLogger struct{}
|
||||
|
||||
func (nopLogger) Logf(classification logging.Classification, format string, v ...any) {}
|
||||
func (nopLogger) Logf(_ logging.Classification, _ string, _ ...any) {}
|
||||
|
||||
// NewClient creates a new S3 client with the provided configuration.
|
||||
// It establishes a connection to the S3-compatible storage service and
|
||||
@@ -105,13 +107,18 @@ type ProgressCallback func(bytesUploaded int64) error
|
||||
// The size parameter must be the exact size of the data to upload.
|
||||
// The progress callback is called periodically with the number of bytes uploaded.
|
||||
// Returns an error if the upload fails.
|
||||
func (c *Client) PutObjectWithProgress(ctx context.Context, key string, data io.Reader, size int64, progress ProgressCallback) error {
|
||||
func (c *Client) PutObjectWithProgress(
|
||||
ctx context.Context, key string, data io.Reader,
|
||||
size int64, progress ProgressCallback,
|
||||
) error {
|
||||
fullKey := c.prefix + key
|
||||
|
||||
// uploadPartSize is 10MB for better progress granularity.
|
||||
const uploadPartSize = 10 * 1024 * 1024
|
||||
|
||||
// Create an uploader with the S3 client
|
||||
uploader := manager.NewUploader(c.s3Client, func(u *manager.Uploader) {
|
||||
// Set part size to 10MB for better progress granularity
|
||||
u.PartSize = 10 * 1024 * 1024
|
||||
u.PartSize = uploadPartSize
|
||||
})
|
||||
|
||||
// Create a progress reader that tracks upload progress
|
||||
@@ -241,7 +248,9 @@ type ObjectInfo struct {
|
||||
// listing is complete or an error occurs. If an error occurs, it will be
|
||||
// sent as the last item with the Err field set. The recursive parameter
|
||||
// is currently unused but reserved for future use.
|
||||
func (c *Client) ListObjectsStream(ctx context.Context, prefix string, recursive bool) <-chan ObjectInfo {
|
||||
func (c *Client) ListObjectsStream(
|
||||
ctx context.Context, prefix string, _ bool,
|
||||
) <-chan ObjectInfo {
|
||||
ch := make(chan ObjectInfo)
|
||||
|
||||
go func() {
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sneak.berlin/go/vaultik/internal/s3"
|
||||
)
|
||||
|
||||
//nolint:paralleltest // test servers share a fixed localhost port
|
||||
func TestClient(t *testing.T) {
|
||||
ts := NewTestServer(t)
|
||||
defer func() {
|
||||
@@ -33,11 +34,21 @@ func TestClient(t *testing.T) {
|
||||
t.Fatalf("failed to create client: %v", err)
|
||||
}
|
||||
|
||||
// Test PutObject
|
||||
testKey := "foo/bar.txt"
|
||||
testData := []byte("test data")
|
||||
|
||||
err = client.PutObject(ctx, testKey, bytes.NewReader(testData))
|
||||
verifyPutGetHead(ctx, t, client, testKey, testData)
|
||||
verifyListAndDelete(ctx, t, client, testKey)
|
||||
}
|
||||
|
||||
// verifyPutGetHead uploads an object, reads it back, and checks existence.
|
||||
func verifyPutGetHead(
|
||||
ctx context.Context, t *testing.T, client *s3.Client,
|
||||
testKey string, testData []byte,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
err := client.PutObject(ctx, testKey, bytes.NewReader(testData))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to put object: %v", err)
|
||||
}
|
||||
@@ -72,8 +83,15 @@ func TestClient(t *testing.T) {
|
||||
if !exists {
|
||||
t.Error("expected object to exist")
|
||||
}
|
||||
}
|
||||
|
||||
// verifyListAndDelete lists the object's prefix, deletes it, and checks
|
||||
// it is gone.
|
||||
func verifyListAndDelete(
|
||||
ctx context.Context, t *testing.T, client *s3.Client, testKey string,
|
||||
) {
|
||||
t.Helper()
|
||||
|
||||
// Test ListObjects
|
||||
keys, err := client.ListObjects(ctx, "foo/")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to list objects: %v", err)
|
||||
@@ -94,7 +112,7 @@ func TestClient(t *testing.T) {
|
||||
}
|
||||
|
||||
// Verify deletion
|
||||
exists, err = client.HeadObject(ctx, testKey)
|
||||
exists, err := client.HeadObject(ctx, testKey)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to head object after deletion: %v", err)
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import (
|
||||
// Module exports S3 functionality as an fx module.
|
||||
// It provides automatic dependency injection for the S3 client,
|
||||
// configuring it based on the application's configuration settings.
|
||||
//
|
||||
//nolint:gochecknoglobals // fx module definitions are package globals
|
||||
var Module = fx.Module("s3",
|
||||
fx.Provide(
|
||||
provideClient,
|
||||
@@ -32,7 +34,7 @@ func provideClient(lc fx.Lifecycle, cfg *config.Config) (*Client, error) {
|
||||
}
|
||||
|
||||
lc.Append(fx.Hook{
|
||||
OnStop: func(ctx context.Context) error {
|
||||
OnStop: func(_ context.Context) error {
|
||||
// S3 client doesn't need explicit cleanup
|
||||
return nil
|
||||
},
|
||||
|
||||
@@ -3,10 +3,10 @@ package s3_test
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -37,13 +37,16 @@ type TestServer struct {
|
||||
logBuf *bytes.Buffer
|
||||
}
|
||||
|
||||
// testServerReadHeaderTimeout bounds header reads on the in-process
|
||||
// test server (gosec G112).
|
||||
const testServerReadHeaderTimeout = 5 * time.Second
|
||||
|
||||
// NewTestServer creates and starts a new test server
|
||||
func NewTestServer(t *testing.T) *TestServer {
|
||||
t.Helper()
|
||||
|
||||
// Create temp directory for any file operations
|
||||
tempDir, err := os.MkdirTemp("", "vaultik-s3-test-*")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp dir: %v", err)
|
||||
}
|
||||
tempDir := t.TempDir()
|
||||
|
||||
// Create in-memory backend
|
||||
backend := s3mem.New()
|
||||
@@ -51,14 +54,15 @@ func NewTestServer(t *testing.T) *TestServer {
|
||||
|
||||
// Create HTTP server
|
||||
server := &http.Server{
|
||||
Addr: "localhost:9999",
|
||||
Handler: faker.Server(),
|
||||
Addr: "localhost:9999",
|
||||
Handler: faker.Server(),
|
||||
ReadHeaderTimeout: testServerReadHeaderTimeout,
|
||||
}
|
||||
|
||||
// Start server in background
|
||||
go func() {
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && err != http.ErrServerClosed {
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
t.Logf("test server error: %v", err)
|
||||
}
|
||||
}()
|
||||
@@ -70,6 +74,14 @@ func NewTestServer(t *testing.T) *TestServer {
|
||||
logBuf := &bytes.Buffer{}
|
||||
|
||||
// Create S3 client with custom logger
|
||||
logFn := func(classification logging.Classification, format string, v ...any) {
|
||||
// Capture logs to buffer instead of stdout
|
||||
fmt.Fprintf(logBuf, "SDK %s %s %s\n",
|
||||
time.Now().Format("2006/01/02 15:04:05"),
|
||||
string(classification),
|
||||
fmt.Sprintf(format, v...))
|
||||
}
|
||||
|
||||
cfg, err := config.LoadDefaultConfig(context.Background(),
|
||||
config.WithRegion(testRegion),
|
||||
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
|
||||
@@ -77,14 +89,9 @@ func NewTestServer(t *testing.T) *TestServer {
|
||||
testSecretKey,
|
||||
"",
|
||||
)),
|
||||
config.WithClientLogMode(aws.LogRetries|aws.LogRequestWithBody|aws.LogResponseWithBody),
|
||||
config.WithLogger(logging.LoggerFunc(func(classification logging.Classification, format string, v ...any) {
|
||||
// Capture logs to buffer instead of stdout
|
||||
fmt.Fprintf(logBuf, "SDK %s %s %s\n",
|
||||
time.Now().Format("2006/01/02 15:04:05"),
|
||||
string(classification),
|
||||
fmt.Sprintf(format, v...))
|
||||
})),
|
||||
config.WithClientLogMode(
|
||||
aws.LogRetries|aws.LogRequestWithBody|aws.LogResponseWithBody),
|
||||
config.WithLogger(logging.LoggerFunc(logFn)),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create AWS config: %v", err)
|
||||
@@ -121,17 +128,13 @@ func NewTestServer(t *testing.T) *TestServer {
|
||||
return ts
|
||||
}
|
||||
|
||||
// Cleanup shuts down the server and removes temp directory
|
||||
// Cleanup shuts down the server. The temp directory is removed
|
||||
// automatically by t.TempDir.
|
||||
func (ts *TestServer) Cleanup() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
err := ts.server.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.RemoveAll(ts.tempDir)
|
||||
return ts.server.Shutdown(ctx)
|
||||
}
|
||||
|
||||
// Client returns the S3 client configured for the test server
|
||||
@@ -140,6 +143,8 @@ func (ts *TestServer) Client() *s3.Client {
|
||||
}
|
||||
|
||||
// TestBasicS3Operations tests basic store and retrieve operations
|
||||
//
|
||||
//nolint:paralleltest // test servers share a fixed localhost port
|
||||
func TestBasicS3Operations(t *testing.T) {
|
||||
ts := NewTestServer(t)
|
||||
defer func() {
|
||||
@@ -193,6 +198,8 @@ func TestBasicS3Operations(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestBlobOperations tests blob storage patterns for vaultik
|
||||
//
|
||||
//nolint:paralleltest // test servers share a fixed localhost port
|
||||
func TestBlobOperations(t *testing.T) {
|
||||
ts := NewTestServer(t)
|
||||
defer func() {
|
||||
@@ -257,6 +264,8 @@ func TestBlobOperations(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestMetadataOperations tests metadata storage patterns
|
||||
//
|
||||
//nolint:paralleltest // test servers share a fixed localhost port
|
||||
func TestMetadataOperations(t *testing.T) {
|
||||
ts := NewTestServer(t)
|
||||
defer func() {
|
||||
@@ -286,7 +295,8 @@ func TestMetadataOperations(t *testing.T) {
|
||||
|
||||
// Store manifest
|
||||
manifestKey := filepath.Join("metadata", snapshotID+".manifest.json.zst")
|
||||
manifestData := []byte(`{"snapshot_id":"2024-01-01T12:00:00Z","blob_hashes":["hash1","hash2"]}`)
|
||||
manifestData := []byte(`{"snapshot_id":"2024-01-01T12:00:00Z",` +
|
||||
`"blob_hashes":["hash1","hash2"]}`)
|
||||
|
||||
_, err = client.PutObject(ctx, &s3.PutObjectInput{
|
||||
Bucket: aws.String(testBucket),
|
||||
|
||||
Reference in New Issue
Block a user