package cli import ( "path/filepath" "time" "git.eeqj.de/sneak/secret/internal/secret" "github.com/spf13/afero" ) // vaultStats accumulates statistics while walking vault directories type vaultStats struct { totalSecrets int totalSize int64 oldestTime time.Time latestTime time.Time } // addVersion accumulates size and timestamp info for one version directory func (s *vaultStats) addVersion(fs afero.Fs, versionPath string) { // Add size of encrypted data dataPath := filepath.Join(versionPath, "data.age") stat, err := fs.Stat(dataPath) if err == nil { s.totalSize += stat.Size() } // Add size of metadata metaPath := filepath.Join(versionPath, "metadata.age") stat, err = fs.Stat(metaPath) if err == nil { s.totalSize += stat.Size() } // Track timestamps stat, err = fs.Stat(versionPath) if err == nil { modTime := stat.ModTime() if s.oldestTime.IsZero() || modTime.Before(s.oldestTime) { s.oldestTime = modTime } if s.latestTime.IsZero() || modTime.After(s.latestTime) { s.latestTime = modTime } } } // addSecret accumulates stats for one secret directory func (s *vaultStats) addSecret(fs afero.Fs, secretsPath, secretName string) { s.totalSecrets++ secretPath := filepath.Join(secretsPath, secretName) // Get size and timestamps from all versions versionsPath := filepath.Join(secretPath, "versions") versionEntries, err := afero.ReadDir(fs, versionsPath) if err != nil { secret.Warn("Could not read versions directory for secret", "secret", secretName, "error", err) return } for _, versionEntry := range versionEntries { if !versionEntry.IsDir() { continue } s.addVersion(fs, filepath.Join(versionsPath, versionEntry.Name())) } } // addVault accumulates stats for one vault directory func (s *vaultStats) addVault(fs afero.Fs, vaultsDir, vaultName string) { vaultPath := filepath.Join(vaultsDir, vaultName) secretsPath := filepath.Join(vaultPath, "secrets.d") // Count secrets in this vault secretEntries, err := afero.ReadDir(fs, secretsPath) if err != nil { secret.Warn("Could not read secrets directory for vault", "vault", vaultName, "error", err) return } for _, secretEntry := range secretEntries { if !secretEntry.IsDir() { continue } s.addSecret(fs, secretsPath, secretEntry.Name()) } } // gatherVaultStats collects statistics from all vaults, returning the // total secret count, total size, and oldest/latest secret timestamps func gatherVaultStats( fs afero.Fs, vaultsDir string, ) (int, int64, time.Time, time.Time, error) { vaultEntries, err := afero.ReadDir(fs, vaultsDir) if err != nil { return 0, 0, time.Time{}, time.Time{}, err } var stats vaultStats for _, vaultEntry := range vaultEntries { if !vaultEntry.IsDir() { continue } stats.addVault(fs, vaultsDir, vaultEntry.Name()) } return stats.totalSecrets, stats.totalSize, stats.oldestTime, stats.latestTime, nil }