Address all four review concerns on PR #31: 1. Fix missed bare fmt.Println() in VerifySnapshotWithOptions (line 620) 2. Replace all direct fmt.Fprintf(v.Stdout,...) / fmt.Fprintln(v.Stdout,...) / fmt.Fscanln(v.Stdin,...) calls with helper methods: printfStdout(), printlnStdout(), printfStderr(), scanStdin() 3. Route progress bar and stderr output through v.Stderr instead of os.Stderr in restore.go (concern #4: v.Stderr now actually used) 4. Rename exported Outputf to unexported printfStdout (YAGNI: only helpers actually used are created)
183 lines
4.9 KiB
Go
183 lines
4.9 KiB
Go
package vaultik
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"git.eeqj.de/sneak/vaultik/internal/config"
|
|
"git.eeqj.de/sneak/vaultik/internal/crypto"
|
|
"git.eeqj.de/sneak/vaultik/internal/database"
|
|
"git.eeqj.de/sneak/vaultik/internal/globals"
|
|
"git.eeqj.de/sneak/vaultik/internal/snapshot"
|
|
"git.eeqj.de/sneak/vaultik/internal/storage"
|
|
"github.com/spf13/afero"
|
|
"go.uber.org/fx"
|
|
)
|
|
|
|
// Vaultik contains all dependencies needed for vaultik operations
|
|
type Vaultik struct {
|
|
Globals *globals.Globals
|
|
Config *config.Config
|
|
DB *database.DB
|
|
Repositories *database.Repositories
|
|
Storage storage.Storer
|
|
ScannerFactory snapshot.ScannerFactory
|
|
SnapshotManager *snapshot.SnapshotManager
|
|
Shutdowner fx.Shutdowner
|
|
Fs afero.Fs
|
|
|
|
// Context management
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
|
|
// IO
|
|
Stdout io.Writer
|
|
Stderr io.Writer
|
|
Stdin io.Reader
|
|
}
|
|
|
|
// VaultikParams contains all parameters for New that can be provided by fx
|
|
type VaultikParams struct {
|
|
fx.In
|
|
|
|
Globals *globals.Globals
|
|
Config *config.Config
|
|
DB *database.DB
|
|
Repositories *database.Repositories
|
|
Storage storage.Storer
|
|
ScannerFactory snapshot.ScannerFactory
|
|
SnapshotManager *snapshot.SnapshotManager
|
|
Shutdowner fx.Shutdowner
|
|
Fs afero.Fs `optional:"true"`
|
|
}
|
|
|
|
// New creates a new Vaultik instance with proper context management
|
|
// It automatically includes crypto capabilities if age_secret_key is configured
|
|
func New(params VaultikParams) *Vaultik {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
// Use provided filesystem or default to OS filesystem
|
|
fs := params.Fs
|
|
if fs == nil {
|
|
fs = afero.NewOsFs()
|
|
}
|
|
|
|
// Set filesystem on SnapshotManager
|
|
params.SnapshotManager.SetFilesystem(fs)
|
|
|
|
return &Vaultik{
|
|
Globals: params.Globals,
|
|
Config: params.Config,
|
|
DB: params.DB,
|
|
Repositories: params.Repositories,
|
|
Storage: params.Storage,
|
|
ScannerFactory: params.ScannerFactory,
|
|
SnapshotManager: params.SnapshotManager,
|
|
Shutdowner: params.Shutdowner,
|
|
Fs: fs,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
Stdout: os.Stdout,
|
|
Stderr: os.Stderr,
|
|
Stdin: os.Stdin,
|
|
}
|
|
}
|
|
|
|
// Context returns the Vaultik's context
|
|
func (v *Vaultik) Context() context.Context {
|
|
return v.ctx
|
|
}
|
|
|
|
// SetContext sets the Vaultik's context (primarily for testing)
|
|
func (v *Vaultik) SetContext(ctx context.Context) {
|
|
v.ctx = ctx
|
|
}
|
|
|
|
// Cancel cancels the Vaultik's context
|
|
func (v *Vaultik) Cancel() {
|
|
v.cancel()
|
|
}
|
|
|
|
// CanDecrypt returns true if this Vaultik instance has decryption capabilities
|
|
func (v *Vaultik) CanDecrypt() bool {
|
|
return v.Config.AgeSecretKey != ""
|
|
}
|
|
|
|
// GetEncryptor creates a new Encryptor instance based on the configured age recipients
|
|
// Returns an error if no recipients are configured
|
|
func (v *Vaultik) GetEncryptor() (*crypto.Encryptor, error) {
|
|
if len(v.Config.AgeRecipients) == 0 {
|
|
return nil, fmt.Errorf("no age recipients configured")
|
|
}
|
|
return crypto.NewEncryptor(v.Config.AgeRecipients)
|
|
}
|
|
|
|
// GetDecryptor creates a new Decryptor instance based on the configured age secret key
|
|
// Returns an error if no secret key is configured
|
|
func (v *Vaultik) GetDecryptor() (*crypto.Decryptor, error) {
|
|
if v.Config.AgeSecretKey == "" {
|
|
return nil, fmt.Errorf("no age secret key configured")
|
|
}
|
|
return crypto.NewDecryptor(v.Config.AgeSecretKey)
|
|
}
|
|
|
|
// GetFilesystem returns the filesystem instance used by Vaultik
|
|
func (v *Vaultik) GetFilesystem() afero.Fs {
|
|
return v.Fs
|
|
}
|
|
|
|
// printfStdout writes formatted output to stdout for user-facing messages.
|
|
func (v *Vaultik) printfStdout(format string, args ...any) {
|
|
_, _ = fmt.Fprintf(v.Stdout, format, args...)
|
|
}
|
|
|
|
|
|
// printlnStdout writes a line to stdout.
|
|
func (v *Vaultik) printlnStdout(args ...any) {
|
|
_, _ = fmt.Fprintln(v.Stdout, args...)
|
|
}
|
|
|
|
// printfStderr writes formatted output to stderr.
|
|
func (v *Vaultik) printfStderr(format string, args ...any) {
|
|
_, _ = fmt.Fprintf(v.Stderr, format, args...)
|
|
}
|
|
|
|
// scanStdin reads a line of input from stdin.
|
|
func (v *Vaultik) scanStdin(a ...any) (int, error) {
|
|
return fmt.Fscanln(v.Stdin, a...)
|
|
}
|
|
|
|
// TestVaultik wraps a Vaultik with captured stdout/stderr for testing
|
|
type TestVaultik struct {
|
|
*Vaultik
|
|
Stdout *bytes.Buffer
|
|
Stderr *bytes.Buffer
|
|
Stdin *bytes.Buffer
|
|
}
|
|
|
|
// NewForTesting creates a minimal Vaultik instance for testing purposes.
|
|
// Only the Storage field is populated; other fields are nil.
|
|
// Returns a TestVaultik that captures stdout/stderr in buffers.
|
|
func NewForTesting(storage storage.Storer) *TestVaultik {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
stdout := &bytes.Buffer{}
|
|
stderr := &bytes.Buffer{}
|
|
stdin := &bytes.Buffer{}
|
|
return &TestVaultik{
|
|
Vaultik: &Vaultik{
|
|
Storage: storage,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
Stdout: stdout,
|
|
Stderr: stderr,
|
|
Stdin: stdin,
|
|
},
|
|
Stdout: stdout,
|
|
Stderr: stderr,
|
|
Stdin: stdin,
|
|
}
|
|
}
|