Skip to content

Commit 11b361a

Browse files
authored
feat(cache): add management commands (#29)
1 parent bd8348b commit 11b361a

File tree

2 files changed

+242
-39
lines changed

2 files changed

+242
-39
lines changed

cmd/kuba/cache.go

Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package kuba
22

33
import (
44
"fmt"
5+
"os"
56
"path/filepath"
67
"strings"
78

@@ -12,6 +13,7 @@ import (
1213

1314
var (
1415
cachePath string
16+
cacheName string
1517
cacheEnv string
1618
cacheVerbose bool
1719
)
@@ -50,11 +52,15 @@ var cacheClearCmd = &cobra.Command{
5052
Short: "Clear cached secrets",
5153
Long: `Clear cached secrets.
5254
53-
By default, clears all cached secrets. Use --path to clear secrets for a specific
54-
kuba.yaml file, or --env to clear secrets for a specific environment.`,
55+
By default, clears secrets from ./kuba.yaml in the current directory. Use filters to clear specific entries:
56+
- --path: Clear secrets for a specific kuba.yaml file (defaults to ./kuba.yaml)
57+
- --env: Clear secrets for a specific kuba environment
58+
- --name: Clear secrets for a specific environment name
59+
- --all: Clear all cached secrets from all paths
60+
- --expired: Clear only expired secrets`,
5561
Args: cobra.NoArgs,
5662
RunE: func(cmd *cobra.Command, args []string) error {
57-
return runCacheClear()
63+
return runCacheClear(cmd)
5864
},
5965
}
6066

@@ -68,6 +74,25 @@ var cacheStatsCmd = &cobra.Command{
6874
},
6975
}
7076

77+
var cacheExpireCmd = &cobra.Command{
78+
Use: "expire",
79+
Short: "Set expiry time for cached secrets",
80+
Long: `Set expiry time for cached secrets.
81+
82+
This command allows you to update the expiry time for existing cache entries.
83+
You can filter by path, env, or name, and set a new expiry time using
84+
human-readable format (e.g., "2w", "1d", "72h", "1y").
85+
86+
Examples:
87+
kuba cache expire --path kuba.yaml --ttl 2w
88+
kuba cache expire --name production --ttl 1d
89+
kuba cache expire --env staging --ttl 72h`,
90+
Args: cobra.NoArgs,
91+
RunE: func(cmd *cobra.Command, args []string) error {
92+
return runCacheExpire(cmd)
93+
},
94+
}
95+
7196
var configCacheCmd = &cobra.Command{
7297
Use: "cache",
7398
Short: "Configure cache settings",
@@ -91,16 +116,22 @@ func init() {
91116
cacheCmd.AddCommand(cacheListCmd)
92117
cacheCmd.AddCommand(cacheClearCmd)
93118
cacheCmd.AddCommand(cacheStatsCmd)
119+
cacheCmd.AddCommand(cacheExpireCmd)
94120

95121
// Global flags for cache commands
96122
cacheCmd.PersistentFlags().StringVarP(&cachePath, "path", "p", "", "Path to kuba.yaml file")
97-
cacheCmd.PersistentFlags().StringVarP(&cacheEnv, "env", "e", "", "Environment name")
123+
cacheCmd.PersistentFlags().StringVarP(&cacheEnv, "env", "e", "", "Kuba environment name")
124+
cacheCmd.PersistentFlags().StringVarP(&cacheName, "name", "n", "", "Environment name")
98125
cacheCmd.PersistentFlags().BoolVarP(&cacheVerbose, "verbose", "v", false, "Verbose output")
99126

100127
// Cache clear flags
101128
cacheClearCmd.Flags().Bool("all", false, "Clear all cached secrets")
102129
cacheClearCmd.Flags().Bool("expired", false, "Clear only expired secrets")
103130

131+
// Cache expire flags
132+
cacheExpireCmd.Flags().String("ttl", "", "Set new expiry time (e.g., 2w, 1d, 72h, 1y)")
133+
cacheExpireCmd.MarkFlagRequired("ttl")
134+
104135
// Cache config flags (moved to config command)
105136
configCacheCmd.Flags().Bool("enable", false, "Enable caching")
106137
configCacheCmd.Flags().Bool("disable", false, "Disable caching")
@@ -200,52 +231,64 @@ func runCacheList() error {
200231
return nil
201232
}
202233

203-
func runCacheClear() error {
204-
// Load global config
205-
globalConfig, err := config.LoadGlobalConfig()
206-
if err != nil {
207-
return fmt.Errorf("failed to load global config: %w", err)
208-
}
209-
210-
// Convert to cache types
211-
cacheGlobalConfig := &cache.GlobalConfig{
212-
Cache: cache.CacheConfig{
213-
Enabled: globalConfig.Cache.Enabled,
214-
TTL: globalConfig.Cache.TTL,
215-
},
216-
}
234+
func runCacheClear(cmd *cobra.Command) error {
235+
// Get flags
236+
all, _ := cmd.Flags().GetBool("all")
237+
expired, _ := cmd.Flags().GetBool("expired")
217238

218-
// Initialize cache manager
219-
manager, err := cache.NewManager(cacheGlobalConfig)
239+
// Initialize cache
240+
cacheInstance, err := cache.NewCache()
220241
if err != nil {
221-
return fmt.Errorf("failed to initialize cache manager: %w", err)
242+
return fmt.Errorf("failed to initialize cache: %w", err)
222243
}
223-
defer manager.Close()
244+
defer cacheInstance.Close()
224245

225-
if !manager.IsEnabled() {
226-
fmt.Println("Caching is disabled.")
227-
return nil
246+
// Set default path if not provided
247+
pathToUse := cachePath
248+
if pathToUse == "" {
249+
// Get current working directory
250+
cwd, err := os.Getwd()
251+
if err != nil {
252+
return fmt.Errorf("failed to get current working directory: %w", err)
253+
}
254+
pathToUse = filepath.Join(cwd, "kuba.yaml")
228255
}
229256

230257
// Determine what to clear
231-
if cachePath != "" && cacheEnv != "" {
232-
// Clear specific environment
233-
if err := manager.ClearByEnvironment(cachePath, cacheEnv); err != nil {
234-
return fmt.Errorf("failed to clear cache for environment: %w", err)
235-
}
236-
fmt.Printf("Cleared cache for environment '%s' in %s\n", cacheEnv, cachePath)
237-
} else if cachePath != "" {
238-
// Clear specific path
239-
if err := manager.ClearByPath(cachePath); err != nil {
240-
return fmt.Errorf("failed to clear cache for path: %w", err)
241-
}
242-
fmt.Printf("Cleared cache for %s\n", cachePath)
243-
} else {
258+
var count int
259+
if all {
244260
// Clear all
245-
if err := manager.Clear(); err != nil {
261+
if err := cacheInstance.Clear(); err != nil {
246262
return fmt.Errorf("failed to clear cache: %w", err)
247263
}
248264
fmt.Println("Cleared all cached secrets.")
265+
} else {
266+
// Clear filtered entries
267+
count, err = cacheInstance.ClearFiltered(pathToUse, cacheEnv, cacheName, expired)
268+
if err != nil {
269+
return fmt.Errorf("failed to clear cache entries: %w", err)
270+
}
271+
272+
// Show results
273+
fmt.Printf("Cleared %d cache entries\n", count)
274+
275+
// Show filters used
276+
filters := []string{}
277+
if pathToUse != "" {
278+
filters = append(filters, fmt.Sprintf("path=%s", pathToUse))
279+
}
280+
if cacheEnv != "" {
281+
filters = append(filters, fmt.Sprintf("env=%s", cacheEnv))
282+
}
283+
if cacheName != "" {
284+
filters = append(filters, fmt.Sprintf("name=%s", cacheName))
285+
}
286+
if expired {
287+
filters = append(filters, "expired=true")
288+
}
289+
if len(filters) > 0 {
290+
fmt.Printf("Filters applied: %s\n", strings.Join(filters, ", "))
291+
}
249292
}
250293

251294
return nil
@@ -362,6 +405,56 @@ func runCacheConfigWithCmd(cmd *cobra.Command) error {
362405
return nil
363406
}
364407

408+
func runCacheExpire(cmd *cobra.Command) error {
409+
// Get TTL from flag
410+
ttlStr, _ := cmd.Flags().GetString("ttl")
411+
if ttlStr == "" {
412+
return fmt.Errorf("--ttl is required")
413+
}
414+
415+
// Parse TTL
416+
duration, _, err := cache.ParseDuration(ttlStr)
417+
if err != nil {
418+
return fmt.Errorf("invalid TTL format: %w", err)
419+
}
420+
421+
// Initialize cache
422+
cacheInstance, err := cache.NewCache()
423+
if err != nil {
424+
return fmt.Errorf("failed to initialize cache: %w", err)
425+
}
426+
defer cacheInstance.Close()
427+
428+
// Update expiry for filtered entries
429+
count, err := cacheInstance.UpdateExpiry(cachePath, cacheEnv, cacheName, duration)
430+
if err != nil {
431+
return fmt.Errorf("failed to update cache expiry: %w", err)
432+
}
433+
434+
// Show results
435+
fmt.Printf("Updated expiry for %d cache entries\n", count)
436+
fmt.Printf("New TTL: %s\n", duration)
437+
438+
// Show filters used
439+
filters := []string{}
440+
if cachePath != "" {
441+
filters = append(filters, fmt.Sprintf("path=%s", cachePath))
442+
}
443+
if cacheEnv != "" {
444+
filters = append(filters, fmt.Sprintf("env=%s", cacheEnv))
445+
}
446+
if cacheName != "" {
447+
filters = append(filters, fmt.Sprintf("name=%s", cacheName))
448+
}
449+
if len(filters) > 0 {
450+
fmt.Printf("Filters applied: %s\n", strings.Join(filters, ", "))
451+
} else {
452+
fmt.Println("Applied to all cache entries")
453+
}
454+
455+
return nil
456+
}
457+
365458
// maskSecret masks a secret value for display
366459
func maskSecret(value string) string {
367460
if len(value) == 0 {

internal/lib/cache/cache.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"path/filepath"
88
"runtime"
9+
"strings"
910
"time"
1011

1112
_ "github.com/mattn/go-sqlite3"
@@ -192,6 +193,115 @@ func (c *Cache) List() ([]CacheEntry, error) {
192193
return entries, nil
193194
}
194195

196+
// ClearFiltered clears cache entries based on filters
197+
func (c *Cache) ClearFiltered(path, kubaEnv, env string, expiredOnly bool) (int, error) {
198+
logger := log.NewLogger()
199+
200+
// Build WHERE clause based on filters
201+
var conditions []string
202+
var args []interface{}
203+
argIndex := 1
204+
205+
if path != "" {
206+
conditions = append(conditions, fmt.Sprintf("path = $%d", argIndex))
207+
args = append(args, path)
208+
argIndex++
209+
}
210+
211+
if kubaEnv != "" {
212+
conditions = append(conditions, fmt.Sprintf("kuba_env = $%d", argIndex))
213+
args = append(args, kubaEnv)
214+
argIndex++
215+
}
216+
217+
if env != "" {
218+
conditions = append(conditions, fmt.Sprintf("env = $%d", argIndex))
219+
args = append(args, env)
220+
argIndex++
221+
}
222+
223+
if expiredOnly {
224+
conditions = append(conditions, fmt.Sprintf("expires_at < $%d", argIndex))
225+
args = append(args, time.Now())
226+
argIndex++
227+
}
228+
229+
// Build query
230+
whereClause := ""
231+
if len(conditions) > 0 {
232+
whereClause = "WHERE " + strings.Join(conditions, " AND ")
233+
}
234+
235+
query := fmt.Sprintf("DELETE FROM secrets %s", whereClause)
236+
237+
result, err := c.db.Exec(query, args...)
238+
if err != nil {
239+
return 0, fmt.Errorf("failed to clear cache entries: %w", err)
240+
}
241+
242+
rowsAffected, err := result.RowsAffected()
243+
if err != nil {
244+
return 0, fmt.Errorf("failed to get rows affected: %w", err)
245+
}
246+
247+
logger.Debug("Cleared cache entries", "count", rowsAffected, "path", path, "kuba_env", kubaEnv, "env", env, "expired_only", expiredOnly)
248+
return int(rowsAffected), nil
249+
}
250+
251+
// UpdateExpiry updates the expiry time for cache entries based on filters
252+
func (c *Cache) UpdateExpiry(path, kubaEnv, env string, newTTL time.Duration) (int, error) {
253+
logger := log.NewLogger()
254+
255+
// Build WHERE clause based on filters
256+
var conditions []string
257+
var args []interface{}
258+
argIndex := 1
259+
260+
if path != "" {
261+
conditions = append(conditions, fmt.Sprintf("path = $%d", argIndex))
262+
args = append(args, path)
263+
argIndex++
264+
}
265+
266+
if kubaEnv != "" {
267+
conditions = append(conditions, fmt.Sprintf("kuba_env = $%d", argIndex))
268+
args = append(args, kubaEnv)
269+
argIndex++
270+
}
271+
272+
if env != "" {
273+
conditions = append(conditions, fmt.Sprintf("env = $%d", argIndex))
274+
args = append(args, env)
275+
argIndex++
276+
}
277+
278+
// Build query - set new expiry time to now + TTL
279+
newExpiryTime := time.Now().Add(newTTL)
280+
conditions = append(conditions, fmt.Sprintf("expires_at = $%d", argIndex))
281+
args = append(args, newExpiryTime)
282+
argIndex++
283+
284+
whereClause := ""
285+
if len(conditions) > 1 { // More than just the expiry condition
286+
whereClause = "WHERE " + strings.Join(conditions[:len(conditions)-1], " AND ")
287+
}
288+
289+
query := fmt.Sprintf("UPDATE secrets SET expires_at = $%d %s", argIndex, whereClause)
290+
291+
result, err := c.db.Exec(query, args...)
292+
if err != nil {
293+
return 0, fmt.Errorf("failed to update cache expiry: %w", err)
294+
}
295+
296+
rowsAffected, err := result.RowsAffected()
297+
if err != nil {
298+
return 0, fmt.Errorf("failed to get rows affected: %w", err)
299+
}
300+
301+
logger.Debug("Updated cache expiry", "count", rowsAffected, "path", path, "kuba_env", kubaEnv, "env", env, "new_ttl", newTTL, "new_expiry", newExpiryTime)
302+
return int(rowsAffected), nil
303+
}
304+
195305
// getCacheDir returns the cache directory path
196306
func getCacheDir() (string, error) {
197307
homeDir, err := os.UserHomeDir()

0 commit comments

Comments
 (0)