Skip to content

Commit 917d2c7

Browse files
grokifyclaude
andcommitted
refactor: update cmd/omnivault to use new internal packages
Simplify cmd/omnivault by delegating to extracted packages: - output.go: thin wrapper around internal/output.Writer - expiry.go: delegate to internal/expiry.Check and WriteWarning - secrets.go: use WriteSearch() instead of direct format handling Reduces code duplication and improves maintainability. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent ae8efd3 commit 917d2c7

3 files changed

Lines changed: 24 additions & 327 deletions

File tree

cmd/omnivault/expiry.go

Lines changed: 4 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package main
22

33
import (
4-
"fmt"
54
"os"
6-
"time"
75

86
"github.com/plexusone/omnivault/internal/config"
97
"github.com/plexusone/omnivault/internal/daemon"
8+
"github.com/plexusone/omnivault/internal/expiry"
109
)
1110

1211
// checkExpiryWarning checks if a secret is expired or expiring soon
@@ -16,65 +15,14 @@ func checkExpiryWarning(secret *daemon.SecretResponse) error {
1615
return nil
1716
}
1817

19-
now := time.Now()
20-
expiresAt := secret.ExpiresAt
21-
22-
// Check if already expired
23-
if expiresAt.Before(now) {
24-
fmt.Fprintf(os.Stderr, "WARNING: Secret '%s' has expired (expired %s ago)\n",
25-
secret.Path, formatDuration(now.Sub(expiresAt)))
26-
return fmt.Errorf("secret expired")
27-
}
28-
29-
// Check if expiring soon
18+
// Load config for warning threshold
3019
cfg, err := config.LoadConfig()
3120
if err != nil {
3221
cfg = config.DefaultConfig()
3322
}
3423

3524
warningDays := cfg.GetExpiryWarningDays()
36-
warningThreshold := time.Duration(warningDays) * 24 * time.Hour
37-
timeUntilExpiry := expiresAt.Sub(now)
38-
39-
if timeUntilExpiry <= warningThreshold {
40-
fmt.Fprintf(os.Stderr, "WARNING: Secret '%s' expires in %s (on %s)\n",
41-
secret.Path, formatDuration(timeUntilExpiry), expiresAt.Format("2006-01-02"))
42-
return nil
43-
}
44-
45-
return nil
46-
}
47-
48-
// formatDuration formats a duration in a human-readable way.
49-
func formatDuration(d time.Duration) string {
50-
if d < 0 {
51-
d = -d
52-
}
53-
54-
days := int(d.Hours() / 24)
55-
hours := int(d.Hours()) % 24
56-
minutes := int(d.Minutes()) % 60
57-
58-
if days > 0 {
59-
if days == 1 {
60-
return "1 day"
61-
}
62-
return fmt.Sprintf("%d days", days)
63-
}
64-
65-
if hours > 0 {
66-
if hours == 1 {
67-
return "1 hour"
68-
}
69-
return fmt.Sprintf("%d hours", hours)
70-
}
71-
72-
if minutes > 0 {
73-
if minutes == 1 {
74-
return "1 minute"
75-
}
76-
return fmt.Sprintf("%d minutes", minutes)
77-
}
25+
result := expiry.CheckNow(secret.ExpiresAt, warningDays)
7826

79-
return "less than a minute"
27+
return expiry.WriteWarning(os.Stderr, secret.Path, result)
8028
}

cmd/omnivault/output.go

Lines changed: 19 additions & 252 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,36 @@
11
package main
22

33
import (
4-
"encoding/json"
5-
"fmt"
6-
"io"
7-
"os"
8-
"sort"
9-
"strings"
10-
"time"
11-
12-
"gopkg.in/yaml.v3"
13-
144
"github.com/plexusone/omnivault/internal/config"
155
"github.com/plexusone/omnivault/internal/daemon"
6+
"github.com/plexusone/omnivault/internal/output"
167
)
178

18-
// OutputFormat represents the output format type.
19-
type OutputFormat int
9+
// OutputFormat is an alias to internal/output.Format for backwards compatibility.
10+
type OutputFormat = output.Format
2011

12+
// Format constants for backwards compatibility.
2113
const (
22-
// FormatText is the default human-readable format.
23-
FormatText OutputFormat = iota
24-
// FormatJSON outputs JSON.
25-
FormatJSON
26-
// FormatYAML outputs YAML.
27-
FormatYAML
28-
// FormatShell outputs shell-sourceable format.
29-
FormatShell
14+
FormatText = output.FormatText
15+
FormatJSON = output.FormatJSON
16+
FormatYAML = output.FormatYAML
17+
FormatShell = output.FormatShell
3018
)
3119

32-
// ParseFormat parses a format string into an OutputFormat.
20+
// ParseFormat delegates to internal/output.ParseFormat.
3321
func ParseFormat(s string) OutputFormat {
34-
switch strings.ToLower(s) {
35-
case "json":
36-
return FormatJSON
37-
case "yaml", "yml":
38-
return FormatYAML
39-
case "shell", "sh", "bash":
40-
return FormatShell
41-
default:
42-
return FormatText
43-
}
22+
return output.ParseFormat(s)
4423
}
4524

46-
// OutputWriter writes formatted output.
25+
// OutputWriter wraps internal/output.Writer with cmd-specific functionality.
4726
type OutputWriter struct {
48-
format OutputFormat
49-
w io.Writer
27+
*output.Writer
5028
}
5129

5230
// NewOutputWriter creates a new OutputWriter.
5331
func NewOutputWriter(format OutputFormat) *OutputWriter {
5432
return &OutputWriter{
55-
format: format,
56-
w: os.Stdout,
33+
Writer: output.NewWriter(format),
5734
}
5835
}
5936

@@ -72,231 +49,21 @@ func NewOutputWriterFromFlags(flags *ParsedFlags) *OutputWriter {
7249
}
7350

7451
// WriteSecret writes a secret in the configured format.
75-
// If fieldName is non-empty, only that field is output.
7652
func (o *OutputWriter) WriteSecret(secret *daemon.SecretResponse, fieldName string) error {
77-
// Field extraction mode
78-
if fieldName != "" {
79-
return o.writeFieldValue(secret, fieldName)
80-
}
81-
82-
switch o.format {
83-
case FormatJSON:
84-
return o.writeJSON(secret)
85-
case FormatYAML:
86-
return o.writeYAML(secret)
87-
case FormatShell:
88-
return o.writeShellSecret(secret)
89-
default:
90-
return o.writeTextSecret(secret)
91-
}
92-
}
93-
94-
// writeFieldValue outputs only a specific field value.
95-
func (o *OutputWriter) writeFieldValue(secret *daemon.SecretResponse, fieldName string) error {
96-
var value string
97-
98-
if fieldName == "value" || fieldName == "" {
99-
value = secret.Value
100-
} else if secret.Fields != nil {
101-
value = secret.Fields[fieldName]
102-
}
103-
104-
switch o.format {
105-
case FormatJSON:
106-
return o.writeJSON(map[string]string{fieldName: value})
107-
case FormatYAML:
108-
return o.writeYAML(map[string]string{fieldName: value})
109-
case FormatShell:
110-
fmt.Fprintf(o.w, "export %s=%s\n", shellSafeKey(fieldName), shellQuote(value))
111-
return nil
112-
default:
113-
fmt.Fprintln(o.w, value)
114-
return nil
115-
}
116-
}
117-
118-
// writeTextSecret writes a secret in human-readable text format.
119-
func (o *OutputWriter) writeTextSecret(secret *daemon.SecretResponse) error {
120-
if secret.Value != "" {
121-
fmt.Fprintln(o.w, secret.Value)
122-
}
123-
124-
if len(secret.Fields) > 0 {
125-
keys := sortedKeys(secret.Fields)
126-
for _, k := range keys {
127-
fmt.Fprintf(o.w, "%s: %s\n", k, secret.Fields[k])
128-
}
129-
}
130-
131-
return nil
132-
}
133-
134-
// writeShellSecret writes a secret as shell-sourceable export statements.
135-
func (o *OutputWriter) writeShellSecret(secret *daemon.SecretResponse) error {
136-
// Use path as variable name prefix
137-
prefix := shellSafeKey(secret.Path)
138-
139-
if secret.Value != "" {
140-
fmt.Fprintf(o.w, "export %s=%s\n", prefix, shellQuote(secret.Value))
141-
}
142-
143-
if len(secret.Fields) > 0 {
144-
keys := sortedKeys(secret.Fields)
145-
for _, k := range keys {
146-
varName := prefix + "_" + shellSafeKey(k)
147-
fmt.Fprintf(o.w, "export %s=%s\n", varName, shellQuote(secret.Fields[k]))
148-
}
149-
}
150-
151-
return nil
53+
return o.Writer.WriteSecret(secret, fieldName)
15254
}
15355

15456
// WriteList writes a list response in the configured format.
15557
func (o *OutputWriter) WriteList(resp *daemon.ListResponse, showMetadata bool) error {
156-
switch o.format {
157-
case FormatJSON:
158-
return o.writeJSON(resp)
159-
case FormatYAML:
160-
return o.writeYAML(resp)
161-
default:
162-
return o.writeTextList(resp, showMetadata)
163-
}
164-
}
165-
166-
// writeTextList writes a list in human-readable text format.
167-
func (o *OutputWriter) writeTextList(resp *daemon.ListResponse, showMetadata bool) error {
168-
if resp.Count == 0 {
169-
fmt.Fprintln(o.w, "No secrets found")
170-
return nil
171-
}
172-
173-
for _, item := range resp.Secrets {
174-
typeIndicator := ""
175-
if item.HasValue && item.HasFields {
176-
typeIndicator = " (value+fields)"
177-
} else if item.HasFields {
178-
typeIndicator = " (fields)"
179-
}
180-
181-
tagStr := ""
182-
if len(item.Tags) > 0 {
183-
if showMetadata && len(item.TagsMap) > 0 {
184-
// Show full key=value pairs
185-
var pairs []string
186-
for k, v := range item.TagsMap {
187-
pairs = append(pairs, fmt.Sprintf("%s=%s", k, v))
188-
}
189-
sort.Strings(pairs)
190-
tagStr = fmt.Sprintf(" [%s]", strings.Join(pairs, ", "))
191-
} else {
192-
tagStr = fmt.Sprintf(" [%s]", strings.Join(item.Tags, ", "))
193-
}
194-
}
195-
196-
fmt.Fprintf(o.w, "%s%s%s\n", item.Path, typeIndicator, tagStr)
197-
198-
if showMetadata {
199-
if !item.CreatedAt.IsZero() {
200-
fmt.Fprintf(o.w, " Created: %s\n", item.CreatedAt.Format(time.RFC3339))
201-
}
202-
if !item.UpdatedAt.IsZero() {
203-
fmt.Fprintf(o.w, " Updated: %s\n", item.UpdatedAt.Format(time.RFC3339))
204-
}
205-
if !item.ExpiresAt.IsZero() {
206-
fmt.Fprintf(o.w, " Expires: %s\n", item.ExpiresAt.Format(time.RFC3339))
207-
}
208-
}
209-
}
210-
211-
fmt.Fprintf(o.w, "\n%d secret(s)\n", resp.Count)
212-
return nil
58+
return o.Writer.WriteList(resp, showMetadata)
21359
}
21460

21561
// WriteStatus writes a status response in the configured format.
21662
func (o *OutputWriter) WriteStatus(status *daemon.StatusResponse, daemonRunning bool) error {
217-
switch o.format {
218-
case FormatJSON:
219-
return o.writeJSON(status)
220-
case FormatYAML:
221-
return o.writeYAML(status)
222-
default:
223-
return o.writeTextStatus(status, daemonRunning)
224-
}
63+
return o.Writer.WriteStatus(status, daemonRunning)
22564
}
22665

227-
// writeTextStatus writes status in human-readable text format.
228-
func (o *OutputWriter) writeTextStatus(status *daemon.StatusResponse, daemonRunning bool) error {
229-
if !daemonRunning {
230-
fmt.Fprintln(o.w, "Daemon: not running")
231-
return nil
232-
}
233-
234-
fmt.Fprintln(o.w, "Daemon: running")
235-
fmt.Fprintf(o.w, "Uptime: %s\n", status.Uptime)
236-
237-
if !status.VaultExists {
238-
fmt.Fprintln(o.w, "Vault: not initialized")
239-
return nil
240-
}
241-
242-
if status.Locked {
243-
fmt.Fprintln(o.w, "Vault: locked")
244-
} else {
245-
fmt.Fprintln(o.w, "Vault: unlocked")
246-
fmt.Fprintf(o.w, "Secrets: %d\n", status.SecretCount)
247-
if !status.UnlockedAt.IsZero() {
248-
fmt.Fprintf(o.w, "Unlocked at: %s\n", status.UnlockedAt.Format("2006-01-02 15:04:05"))
249-
}
250-
}
251-
252-
return nil
253-
}
254-
255-
// writeJSON writes data as JSON.
256-
func (o *OutputWriter) writeJSON(data any) error {
257-
enc := json.NewEncoder(o.w)
258-
enc.SetIndent("", " ")
259-
return enc.Encode(data)
260-
}
261-
262-
// writeYAML writes data as YAML.
263-
func (o *OutputWriter) writeYAML(data any) error {
264-
enc := yaml.NewEncoder(o.w)
265-
enc.SetIndent(2)
266-
return enc.Encode(data)
267-
}
268-
269-
// shellSafeKey converts a string to a safe shell variable name.
270-
func shellSafeKey(s string) string {
271-
var result strings.Builder
272-
for i, r := range s {
273-
if (r >= 'A' && r <= 'Z') || (r >= 'a' && r <= 'z') || r == '_' {
274-
result.WriteRune(r)
275-
} else if r >= '0' && r <= '9' {
276-
if i == 0 {
277-
result.WriteRune('_')
278-
}
279-
result.WriteRune(r)
280-
} else {
281-
result.WriteRune('_')
282-
}
283-
}
284-
return strings.ToUpper(result.String())
285-
}
286-
287-
// shellQuote quotes a string for safe shell use.
288-
func shellQuote(s string) string {
289-
// Use single quotes and escape any single quotes in the string
290-
escaped := strings.ReplaceAll(s, "'", "'\"'\"'")
291-
return "'" + escaped + "'"
292-
}
293-
294-
// sortedKeys returns the sorted keys of a map.
295-
func sortedKeys(m map[string]string) []string {
296-
keys := make([]string, 0, len(m))
297-
for k := range m {
298-
keys = append(keys, k)
299-
}
300-
sort.Strings(keys)
301-
return keys
66+
// WriteSearch writes a search response in the configured format.
67+
func (o *OutputWriter) WriteSearch(resp *daemon.SearchResponse) error {
68+
return o.Writer.WriteSearch(resp)
30269
}

0 commit comments

Comments
 (0)