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.
143 lines
3.7 KiB
Go
143 lines
3.7 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"go.uber.org/fx"
|
|
"sneak.berlin/go/vaultik/internal/config"
|
|
"sneak.berlin/go/vaultik/internal/s3"
|
|
)
|
|
|
|
// defaultS3Region is used when neither the URL nor the config specify one.
|
|
const defaultS3Region = "us-east-1"
|
|
|
|
// defaultS3Endpoint is the AWS endpoint used when none is configured.
|
|
const defaultS3Endpoint = "s3.amazonaws.com"
|
|
|
|
// Module exports storage functionality as an fx module.
|
|
// It provides a Storer implementation based on the configured storage URL
|
|
// or falls back to legacy S3 configuration.
|
|
//
|
|
//nolint:gochecknoglobals // fx module definitions are package globals
|
|
var Module = fx.Module("storage",
|
|
fx.Provide(NewStorer),
|
|
)
|
|
|
|
// NewStorer creates a Storer based on configuration.
|
|
// If StorageURL is set, it uses URL-based configuration.
|
|
// Otherwise, it falls back to legacy S3 configuration.
|
|
//
|
|
//nolint:ireturn // fx provider intentionally returns the Storer interface
|
|
func NewStorer(cfg *config.Config) (Storer, error) {
|
|
if cfg.StorageURL != "" {
|
|
return storerFromURL(cfg.StorageURL, cfg)
|
|
}
|
|
|
|
return storerFromLegacyS3Config(cfg)
|
|
}
|
|
|
|
//nolint:ireturn // factory intentionally returns the Storer interface
|
|
func storerFromURL(rawURL string, cfg *config.Config) (Storer, error) {
|
|
parsed, err := ParseStorageURL(rawURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing storage URL: %w", err)
|
|
}
|
|
|
|
switch parsed.Scheme {
|
|
case schemeFile:
|
|
return NewFileStorer(parsed.Prefix)
|
|
|
|
case schemeS3:
|
|
return storerFromParsedS3URL(parsed, cfg)
|
|
|
|
case schemeRclone:
|
|
return NewRcloneStorer(
|
|
context.Background(), parsed.RcloneRemote, parsed.Prefix)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("%w: %s", ErrUnsupportedStorage, parsed.Scheme)
|
|
}
|
|
}
|
|
|
|
// storerFromParsedS3URL builds an S3 storer from a parsed s3:// URL,
|
|
// filling endpoint protocol and region defaults from the config.
|
|
//
|
|
//nolint:ireturn // factory intentionally returns the Storer interface
|
|
func storerFromParsedS3URL(parsed *URL, cfg *config.Config) (Storer, error) {
|
|
// Build endpoint URL
|
|
endpoint := parsed.Endpoint
|
|
if endpoint == "" {
|
|
endpoint = defaultS3Endpoint
|
|
}
|
|
|
|
// Add protocol if not present
|
|
hasProtocol := strings.HasPrefix(endpoint, "https://") ||
|
|
strings.HasPrefix(endpoint, "http://")
|
|
if !hasProtocol {
|
|
if parsed.UseSSL {
|
|
endpoint = "https://" + endpoint
|
|
} else {
|
|
endpoint = "http://" + endpoint
|
|
}
|
|
}
|
|
|
|
region := parsed.Region
|
|
if region == "" {
|
|
region = cfg.S3.Region
|
|
if region == "" {
|
|
region = defaultS3Region
|
|
}
|
|
}
|
|
|
|
// Credentials come from config (not URL for security)
|
|
client, err := s3.NewClient(context.Background(), s3.Config{
|
|
Endpoint: endpoint,
|
|
Bucket: parsed.Bucket,
|
|
Prefix: parsed.Prefix,
|
|
AccessKeyID: cfg.S3.AccessKeyID,
|
|
SecretAccessKey: cfg.S3.SecretAccessKey,
|
|
Region: region,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating S3 client: %w", err)
|
|
}
|
|
|
|
return NewS3Storer(client), nil
|
|
}
|
|
|
|
//nolint:ireturn // factory intentionally returns the Storer interface
|
|
func storerFromLegacyS3Config(cfg *config.Config) (Storer, error) {
|
|
endpoint := cfg.S3.Endpoint
|
|
|
|
// Ensure protocol is present
|
|
if !strings.HasPrefix(endpoint, "http://") &&
|
|
!strings.HasPrefix(endpoint, "https://") {
|
|
if cfg.S3.UseSSL {
|
|
endpoint = "https://" + endpoint
|
|
} else {
|
|
endpoint = "http://" + endpoint
|
|
}
|
|
}
|
|
|
|
region := cfg.S3.Region
|
|
if region == "" {
|
|
region = defaultS3Region
|
|
}
|
|
|
|
client, err := s3.NewClient(context.Background(), s3.Config{
|
|
Endpoint: endpoint,
|
|
Bucket: cfg.S3.Bucket,
|
|
Prefix: cfg.S3.Prefix,
|
|
AccessKeyID: cfg.S3.AccessKeyID,
|
|
SecretAccessKey: cfg.S3.SecretAccessKey,
|
|
Region: region,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("creating S3 client: %w", err)
|
|
}
|
|
|
|
return NewS3Storer(client), nil
|
|
}
|