Skip to content

Commit e7d3ae4

Browse files
authored
Merge pull request #18 from jumpstarter-dev/speedup-generate-cipher-key
Cache the generateCipherKey function and add profiling support to lint
2 parents d6b4bd4 + 9d53e09 commit e7d3ae4

File tree

3 files changed

+83
-7
lines changed

3 files changed

+83
-7
lines changed

cmd/lint.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ package main
1818

1919
import (
2020
"fmt"
21+
"os"
22+
"runtime/pprof"
23+
"time"
2124

2225
"github.com/spf13/cobra"
2326

@@ -33,6 +36,7 @@ var lintCmd = &cobra.Command{
3336
SilenceUsage: true,
3437
RunE: func(cmd *cobra.Command, args []string) error {
3538
vaultPassFile, _ := cmd.Flags().GetString("vault-password-file")
39+
cpuProfile, _ := cmd.Flags().GetString("cpu-profile")
3640
// Determine config file path
3741
configFilePath := defaultConfigFile
3842
if len(args) > 0 {
@@ -47,7 +51,37 @@ var lintCmd = &cobra.Command{
4751

4852
fmt.Println("🔍 Validating configuration...")
4953

50-
config_lint.Validate(cfg)
54+
// Start CPU profiling if requested
55+
if cpuProfile != "" {
56+
f, err := os.Create(cpuProfile)
57+
if err != nil {
58+
return fmt.Errorf("could not create CPU profile: %w", err)
59+
}
60+
defer func() {
61+
if closeErr := f.Close(); closeErr != nil {
62+
fmt.Printf("Warning: failed to close profile file: %v\n", closeErr)
63+
}
64+
}()
65+
66+
if err := pprof.StartCPUProfile(f); err != nil {
67+
return fmt.Errorf("could not start CPU profile: %w", err)
68+
}
69+
defer pprof.StopCPUProfile()
70+
71+
fmt.Printf("📊 CPU profiling enabled, output will be saved to: %s\n", cpuProfile)
72+
}
73+
74+
// Time the validation
75+
start := time.Now()
76+
err = config_lint.ValidateWithError(cfg)
77+
duration := time.Since(start)
78+
79+
if err != nil {
80+
fmt.Printf("❌ Configuration validation failed in %v\n", duration)
81+
return err
82+
}
83+
84+
fmt.Printf("✅ Configuration validation completed in %v\n", duration)
5185

5286
return nil
5387
},
@@ -56,6 +90,8 @@ var lintCmd = &cobra.Command{
5690
func init() {
5791
// Add the vault password file flag
5892
lintCmd.Flags().String("vault-password-file", "", "Path to the vault password file for decrypting variables")
93+
// Add the CPU profiling flag
94+
lintCmd.Flags().String("cpu-profile", "", "Enable CPU profiling and save output to the specified file")
5995
// Add the lint command to the root command
6096
rootCmd.AddCommand(lintCmd)
6197
}

internal/config_lint/lint.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,21 @@ func Validate(cfg *config.Config) {
2424
fmt.Println("✅ All configurations are valid")
2525
}
2626

27+
// ValidateWithError checks the loaded configuration for errors and returns an error if any are found.
28+
// This version does not call os.Exit() and is suitable for use with profiling.
29+
func ValidateWithError(cfg *config.Config) error {
30+
errorsByFile := Lint(cfg)
31+
if len(errorsByFile) > 0 {
32+
reportAllErrors(errorsByFile)
33+
return fmt.Errorf("validation failed with %d error(s)", len(errorsByFile))
34+
}
35+
keys := cfg.Loaded.GetVariables().GetAllKeys()
36+
fmt.Printf("📚 Total Variables: %d\n", len(keys))
37+
fmt.Println("")
38+
fmt.Println("✅ All configurations are valid")
39+
return nil
40+
}
41+
2742
func Lint(cfg *config.Config) map[string][]error {
2843
// This function is a placeholder for the linting logic.
2944
// Currently, it only validates cross-references between objects.

internal/vars/vault.go

Lines changed: 31 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"encoding/hex"
99
"errors"
1010
"strings"
11+
"sync"
1112

1213
"golang.org/x/crypto/pbkdf2"
1314
)
@@ -38,6 +39,8 @@ type CipherKey struct {
3839
// VaultDecryptor handles Ansible Vault decryption
3940
type VaultDecryptor struct {
4041
password string
42+
cache map[string]*CipherKey
43+
mutex sync.RWMutex
4144
}
4245

4346
// NewVaultDecryptor creates a new vault decryptor with the given password
@@ -47,7 +50,10 @@ func NewVaultDecryptor(password string) *VaultDecryptor {
4750
password = strings.ReplaceAll(password, "\n", "")
4851
password = strings.ReplaceAll(password, "\r", "")
4952
password = strings.ReplaceAll(password, "\t", "")
50-
return &VaultDecryptor{password: password}
53+
return &VaultDecryptor{
54+
password: password,
55+
cache: make(map[string]*CipherKey),
56+
}
5157
}
5258

5359
// IsVaultEncrypted checks if a variable value is Ansible Vault encrypted
@@ -76,7 +82,7 @@ func (vd *VaultDecryptor) Decrypt(vaultData string) (string, error) {
7682
return "", err
7783
}
7884
// Generate keys
79-
key := generateCipherKey(vd.password, salt)
85+
key := vd.generateCipherKey(salt)
8086

8187
// Verify checksum
8288
if !isChecksumValid(checkSum, encryptedData, key.HMACKey) {
@@ -134,21 +140,40 @@ func parseVaultData(vaultData string) (salt, checkSum, data []byte, err error) {
134140
return salt, checkSum, data, nil
135141
}
136142

137-
// generateCipherKey generates cipher keys from password and salt
138-
func generateCipherKey(password string, salt []byte) *CipherKey {
143+
// generateCipherKey generates cipher keys from password and salt with caching
144+
func (vd *VaultDecryptor) generateCipherKey(salt []byte) *CipherKey {
145+
// Create cache key from salt (password is already stored in the struct)
146+
cacheKey := hex.EncodeToString(salt)
147+
148+
// Try to get from cache first (read lock)
149+
vd.mutex.RLock()
150+
if cached, exists := vd.cache[cacheKey]; exists {
151+
vd.mutex.RUnlock()
152+
return cached
153+
}
154+
vd.mutex.RUnlock()
155+
156+
// Generate new cipher key
139157
k := pbkdf2.Key(
140-
[]byte(password),
158+
[]byte(vd.password),
141159
salt,
142160
iteration,
143161
(cipherKeyLength + hmacKeyLength + ivLength),
144162
sha256.New,
145163
)
146164

147-
return &CipherKey{
165+
cipherKey := &CipherKey{
148166
Key: k[:cipherKeyLength],
149167
HMACKey: k[cipherKeyLength:(cipherKeyLength + hmacKeyLength)],
150168
IV: k[(cipherKeyLength + hmacKeyLength):(cipherKeyLength + hmacKeyLength + ivLength)],
151169
}
170+
171+
// Store in cache (write lock)
172+
vd.mutex.Lock()
173+
vd.cache[cacheKey] = cipherKey
174+
vd.mutex.Unlock()
175+
176+
return cipherKey
152177
}
153178

154179
// isChecksumValid validates HMAC checksum

0 commit comments

Comments
 (0)