From 381e6326eee2dbe65ae9e2f691e70f3f3358e1ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Proch=C3=A1zka?= Date: Sun, 14 Dec 2025 04:13:48 +0100 Subject: [PATCH] feat: template secret builder from multiple secrets This feature adds a dynamic option for secrets customization to needs of the target app. Closes: https://github.com/dirathea/sstart/issues/21 --- examples/.env.templates | 7 ++ examples/.sstart.yml.templates | 15 ++++ internal/app/runner_unix.go | 1 - internal/app/runner_windows.go | 1 - internal/cli/run.go | 4 +- internal/cli/sh.go | 1 - internal/config/config.go | 29 ++++++-- internal/config/config_e2e_test.go | 14 ++-- internal/provider/aws/secretsmanager.go | 21 +----- internal/provider/aws/secretsmanager_test.go | 7 +- .../provider/azurekeyvault/azurekeyvault.go | 21 +----- internal/provider/bitwarden/bitwarden.go | 29 ++------ internal/provider/bitwarden/bitwarden_sm.go | 22 +----- internal/provider/doppler/doppler.go | 21 +----- internal/provider/dotenv/dotenv.go | 35 ++------- internal/provider/dotenv/dotenv_test.go | 20 ++--- internal/provider/gcsm/gcsm.go | 22 +----- internal/provider/gcsm/gcsm_test.go | 6 +- internal/provider/infisical/infisical.go | 21 +----- internal/provider/interface.go | 3 +- internal/provider/onepassword/onepassword.go | 45 +++-------- internal/provider/provider_test.go | 1 - internal/provider/vault/vault.go | 21 +----- internal/provider/vault/vault_test.go | 3 +- internal/secrets/collector.go | 74 ++++++++++++++++++- internal/secrets/collector_test.go | 60 +++++++++++++++ tests/end2end/aws_secretsmanager_test.go | 4 +- tests/end2end/azure_keyvault_test.go | 4 +- tests/end2end/bitwarden_sm_test.go | 2 +- tests/end2end/bitwarden_test.go | 12 +-- tests/end2end/doppler_test.go | 4 +- tests/end2end/format_test.go | 16 ++-- tests/end2end/gcsm_test.go | 4 +- tests/end2end/infisical_test.go | 6 +- tests/end2end/multi_provider_test.go | 6 +- tests/end2end/onepassword_test.go | 16 ++-- tests/end2end/onepassword_testhelpers.go | 7 +- tests/end2end/openbao_test.go | 5 +- tests/end2end/run_test.go | 12 +-- tests/end2end/vault_test.go | 4 +- 40 files changed, 289 insertions(+), 317 deletions(-) create mode 100644 examples/.env.templates create mode 100644 examples/.sstart.yml.templates create mode 100644 internal/secrets/collector_test.go diff --git a/examples/.env.templates b/examples/.env.templates new file mode 100644 index 0000000..a0e8763 --- /dev/null +++ b/examples/.env.templates @@ -0,0 +1,7 @@ +API_KEY=sk_live_1234567890abcdef +APP_NAME=MyAwesomeApp + +PG_USER=user +PG_PASS=password +PG_HOST=localhost +PG_DATABASE=mydb diff --git a/examples/.sstart.yml.templates b/examples/.sstart.yml.templates new file mode 100644 index 0000000..d478c5c --- /dev/null +++ b/examples/.sstart.yml.templates @@ -0,0 +1,15 @@ +# Templates Usage Example +# This demonstrates using templates: It allows you to build a secret as a +# composite of multiple secrets from the source provider + +inherit: false + +providers: + - kind: dotenv + path: .env.templates + keys: + API_KEY: API_SECRET_KEY + APP_NAME: == + templates: # Use classic Go text/template syntax + PG_DSN: | + postgresql://{{ .Env.PG_USER }}:{{ .Env.PG_PASS }}@{{ .Env.PG_HOST }}:5432/{{ .Env.PG_DATABASE }} diff --git a/internal/app/runner_unix.go b/internal/app/runner_unix.go index d27d73a..2d139ff 100644 --- a/internal/app/runner_unix.go +++ b/internal/app/runner_unix.go @@ -21,4 +21,3 @@ func registerSignals(sigChan chan os.Signal) { // Register for interrupt and terminate signals signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) } - diff --git a/internal/app/runner_windows.go b/internal/app/runner_windows.go index d28c248..6030c53 100644 --- a/internal/app/runner_windows.go +++ b/internal/app/runner_windows.go @@ -18,4 +18,3 @@ func registerSignals(sigChan chan os.Signal) { // On Windows, only os.Interrupt (Ctrl+C) is available signal.Notify(sigChan, os.Interrupt) } - diff --git a/internal/cli/run.go b/internal/cli/run.go index eb5091c..0945713 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -10,9 +10,7 @@ import ( "github.com/spf13/cobra" ) -var ( - runProviders []string -) +var runProviders []string var runCmd = &cobra.Command{ Use: "run [flags] -- [args...]", diff --git a/internal/cli/sh.go b/internal/cli/sh.go index 3c46c02..f99ba6c 100644 --- a/internal/cli/sh.go +++ b/internal/cli/sh.go @@ -26,4 +26,3 @@ Example: func init() { rootCmd.AddCommand(shCmd) } - diff --git a/internal/config/config.go b/internal/config/config.go index e0c2859..e34c870 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -3,13 +3,14 @@ package config import ( "fmt" "os" + "text/template" "gopkg.in/yaml.v3" ) // Config represents the main configuration structure type Config struct { - Inherit bool `yaml:"inherit"` // Whether to inherit system environment variables (default: true) + Inherit bool `yaml:"inherit"` // Whether to inherit system environment variables (default: true) Providers []ProviderConfig `yaml:"providers"` } @@ -17,11 +18,12 @@ type Config struct { // Each provider loads from a single source. To load multiple secrets from the same provider type, // configure multiple provider instances with the same 'kind' but different 'id' values. type ProviderConfig struct { - Kind string `yaml:"kind"` - ID string `yaml:"id,omitempty"` // Optional: defaults to 'kind'. Required if multiple providers share the same kind - Config map[string]interface{} `yaml:"-"` // Provider-specific configuration (e.g., path, region, endpoint, etc.) - Keys map[string]string `yaml:"keys,omitempty"` // Optional key mappings (source_key: target_key, or "==" to keep same name) - Env EnvVars `yaml:"env,omitempty"` + Kind string `yaml:"kind"` + ID string `yaml:"id,omitempty"` // Optional: defaults to 'kind'. Required if multiple providers share the same kind + Config map[string]interface{} `yaml:"-"` // Provider-specific configuration (e.g., path, region, endpoint, etc.) + Keys map[string]string `yaml:"keys,omitempty"` // Optional key mappings (source_key: target_key, or "==" to keep same name) + Templates []*template.Template `yaml:"templates,omitempty"` // Optional templates mappings (target_key: str(Go template)) + Env EnvVars `yaml:"env,omitempty"` } // UnmarshalYAML implements custom YAML unmarshaling to capture provider-specific fields @@ -53,6 +55,21 @@ func (p *ProviderConfig) UnmarshalYAML(unmarshal func(interface{}) error) error delete(raw, "keys") } + if templates, ok := raw["templates"].(map[string]interface{}); ok { + for k, v := range templates { + str, ok := v.(string) + if !ok { + return fmt.Errorf("invalid template format") + } + tmpl := template.New(k) + if _, err := tmpl.Parse(str); err != nil { + return err + } + p.Templates = append(p.Templates, tmpl) + } + delete(raw, "templates") + } + if env, ok := raw["env"].(map[string]interface{}); ok { p.Env = make(EnvVars) for k, v := range env { diff --git a/internal/config/config_e2e_test.go b/internal/config/config_e2e_test.go index 4a4c41f..6158cc6 100644 --- a/internal/config/config_e2e_test.go +++ b/internal/config/config_e2e_test.go @@ -154,7 +154,7 @@ providers: // Create temporary YAML file tmpDir := t.TempDir() yamlFile := filepath.Join(tmpDir, "test.yml") - if err := os.WriteFile(yamlFile, []byte(tt.yamlContent), 0644); err != nil { + if err := os.WriteFile(yamlFile, []byte(tt.yamlContent), 0o644); err != nil { t.Fatalf("Failed to create test YAML file: %v", err) } @@ -286,7 +286,7 @@ providers: // Create temporary YAML file tmpDir := t.TempDir() yamlFile := filepath.Join(tmpDir, "test.yml") - if err := os.WriteFile(yamlFile, []byte(tt.yamlContent), 0644); err != nil { + if err := os.WriteFile(yamlFile, []byte(tt.yamlContent), 0o644); err != nil { t.Fatalf("Failed to create test YAML file: %v", err) } @@ -336,7 +336,7 @@ providers: tmpDir := t.TempDir() yamlFile := filepath.Join(tmpDir, "test.yml") - if err := os.WriteFile(yamlFile, []byte(yamlContent), 0644); err != nil { + if err := os.WriteFile(yamlFile, []byte(yamlContent), 0o644); err != nil { t.Fatalf("Failed to create test YAML file: %v", err) } @@ -416,7 +416,7 @@ providers: tmpDir := t.TempDir() yamlFile := filepath.Join(tmpDir, "test.yml") - if err := os.WriteFile(yamlFile, []byte(yamlContent), 0644); err != nil { + if err := os.WriteFile(yamlFile, []byte(yamlContent), 0o644); err != nil { t.Fatalf("Failed to create test YAML file: %v", err) } @@ -473,7 +473,7 @@ providers: tmpDir := t.TempDir() yamlFile := filepath.Join(tmpDir, "test.yml") - if err := os.WriteFile(yamlFile, []byte(yamlContent), 0644); err != nil { + if err := os.WriteFile(yamlFile, []byte(yamlContent), 0o644); err != nil { t.Fatalf("Failed to create test YAML file: %v", err) } @@ -614,7 +614,7 @@ providers: // Create temporary YAML file tmpDir := t.TempDir() yamlFile := filepath.Join(tmpDir, "test.yml") - if err := os.WriteFile(yamlFile, []byte(tt.yamlContent), 0644); err != nil { + if err := os.WriteFile(yamlFile, []byte(tt.yamlContent), 0o644); err != nil { t.Fatalf("Failed to create test YAML file: %v", err) } @@ -643,7 +643,7 @@ providers: // Try to Fetch (will fail for missing connections/credentials, but config parsing should work) ctx := context.Background() - _, err = prov.Fetch(ctx, providerCfg.ID, providerCfg.Config, providerCfg.Keys) + _, err = prov.Fetch(ctx, providerCfg.ID, providerCfg.Config) if (err != nil) != tt.expectParseErr { t.Errorf("Expected parse error: %v, got error: %v", tt.expectParseErr, err) diff --git a/internal/provider/aws/secretsmanager.go b/internal/provider/aws/secretsmanager.go index 087bc5a..8f494a5 100644 --- a/internal/provider/aws/secretsmanager.go +++ b/internal/provider/aws/secretsmanager.go @@ -42,7 +42,7 @@ func (p *SecretsManagerProvider) Name() string { } // Fetch fetches secrets from AWS Secrets Manager -func (p *SecretsManagerProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *SecretsManagerProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -87,26 +87,9 @@ func (p *SecretsManagerProvider) Fetch(ctx context.Context, mapID string, config // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - value := fmt.Sprintf("%v", v) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } diff --git a/internal/provider/aws/secretsmanager_test.go b/internal/provider/aws/secretsmanager_test.go index e247d9c..9a93936 100644 --- a/internal/provider/aws/secretsmanager_test.go +++ b/internal/provider/aws/secretsmanager_test.go @@ -7,8 +7,8 @@ import ( func TestParseConfig(t *testing.T) { tests := []struct { - name string - config map[string]interface{} + name string + config map[string]interface{} wantSecretID string wantRegion string wantEndpoint string @@ -130,7 +130,7 @@ func TestSecretsManagerProvider_Fetch_ConfigValidation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - _, err := provider.Fetch(ctx, "test-map", tt.config, nil) + _, err := provider.Fetch(ctx, "test-map", tt.config) if (err != nil) != tt.wantErr { t.Errorf("SecretsManagerProvider.Fetch() error = %v, wantErr %v", err, tt.wantErr) @@ -261,4 +261,3 @@ func containsSubstring(s, substr string) bool { } return false } - diff --git a/internal/provider/azurekeyvault/azurekeyvault.go b/internal/provider/azurekeyvault/azurekeyvault.go index 0caabbd..e10268c 100644 --- a/internal/provider/azurekeyvault/azurekeyvault.go +++ b/internal/provider/azurekeyvault/azurekeyvault.go @@ -43,7 +43,7 @@ func (p *AzureKeyVaultProvider) Name() string { } // Fetch fetches secrets from Azure Key Vault -func (p *AzureKeyVaultProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *AzureKeyVaultProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -100,26 +100,9 @@ func (p *AzureKeyVaultProvider) Fetch(ctx context.Context, mapID string, config // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - value := fmt.Sprintf("%v", v) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } diff --git a/internal/provider/bitwarden/bitwarden.go b/internal/provider/bitwarden/bitwarden.go index d71b171..99aabe9 100644 --- a/internal/provider/bitwarden/bitwarden.go +++ b/internal/provider/bitwarden/bitwarden.go @@ -96,7 +96,7 @@ func (p *BitwardenProvider) Name() string { } // Fetch fetches secrets from personal Bitwarden vault using REST API -func (p *BitwardenProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *BitwardenProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -218,7 +218,7 @@ func (p *BitwardenProvider) Fetch(ctx context.Context, mapID string, config map[ case "both": // Parse both notes and fields, with fields taking precedence secretData = make(map[string]interface{}) - + // First, parse notes as JSON (if available) if item.Notes != "" { var noteData map[string]interface{} @@ -229,14 +229,14 @@ func (p *BitwardenProvider) Fetch(ctx context.Context, mapID string, config map[ } } } - + // Then, add fields (which will override any duplicate keys from notes) for _, field := range item.Fields { if field.Type == 0 || field.Type == 1 { // Text or Hidden secretData[field.Name] = field.Value } } - + // Also include login credentials if available if item.Login != nil { if item.Login.Username != "" { @@ -246,7 +246,7 @@ func (p *BitwardenProvider) Fetch(ctx context.Context, mapID string, config map[ secretData["password"] = item.Login.Password } } - + if len(secretData) == 0 { return nil, fmt.Errorf("bitwarden item '%s' has no fields or notes for 'both' format", cfg.ItemID) } @@ -283,26 +283,9 @@ func (p *BitwardenProvider) Fetch(ctx context.Context, mapID string, config map[ // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - value := fmt.Sprintf("%v", v) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } diff --git a/internal/provider/bitwarden/bitwarden_sm.go b/internal/provider/bitwarden/bitwarden_sm.go index 68ac0dc..1ab5748 100644 --- a/internal/provider/bitwarden/bitwarden_sm.go +++ b/internal/provider/bitwarden/bitwarden_sm.go @@ -41,7 +41,7 @@ func (p *BitwardenSMProvider) Name() string { // Fetch fetches all secrets from a Bitwarden Secret Manager project // Only Key-Value pairs are extracted from secrets. Note fields are ignored. -func (p *BitwardenSMProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *BitwardenSMProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseSMConfig(config) if err != nil { @@ -124,26 +124,9 @@ func (p *BitwardenSMProvider) Fetch(ctx context.Context, mapID string, config ma // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - value := fmt.Sprintf("%v", v) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } @@ -194,7 +177,6 @@ func (p *BitwardenSMProvider) ensureClient(serverURL, accessToken string) error return nil } - // parseSMConfig converts a map[string]interface{} to BitwardenSMConfig func parseSMConfig(config map[string]interface{}) (*BitwardenSMConfig, error) { // Use JSON marshaling/unmarshaling for clean conversion diff --git a/internal/provider/doppler/doppler.go b/internal/provider/doppler/doppler.go index 9c6ade6..7619cf4 100644 --- a/internal/provider/doppler/doppler.go +++ b/internal/provider/doppler/doppler.go @@ -58,7 +58,7 @@ func (p *DopplerProvider) Name() string { } // Fetch fetches secrets from Doppler -func (p *DopplerProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *DopplerProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Parse and validate configuration cfg, err := validateConfig(config) if err != nil { @@ -122,26 +122,9 @@ func (p *DopplerProvider) Fetch(ctx context.Context, mapID string, config map[st // Use computed value as it resolves secret references (e.g., ${USER}) kvs := make([]provider.KeyValue, 0) for secretName, secretInfo := range response.Secrets { - targetKey := secretName - - // Check if there's a specific mapping - if mappedKey, exists := keys[secretName]; exists { - if mappedKey == "==" { - targetKey = secretName // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = secretName - } else { - // Skip keys not in the mapping - continue - } - // Use computed value (resolves references like ${USER}) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: secretName, Value: secretInfo.Computed, }) } diff --git a/internal/provider/dotenv/dotenv.go b/internal/provider/dotenv/dotenv.go index 61ed31b..f60a526 100644 --- a/internal/provider/dotenv/dotenv.go +++ b/internal/provider/dotenv/dotenv.go @@ -5,8 +5,8 @@ import ( "fmt" "os" - "github.com/joho/godotenv" "github.com/dirathea/sstart/internal/provider" + "github.com/joho/godotenv" ) // DotEnvProvider implements the provider interface for .env files @@ -24,7 +24,7 @@ func (p *DotEnvProvider) Name() string { } // Fetch fetches secrets from a .env file -func (p *DotEnvProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *DotEnvProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Extract path from config path, ok := config["path"].(string) if !ok || path == "" { @@ -40,32 +40,13 @@ func (p *DotEnvProvider) Fetch(ctx context.Context, mapID string, config map[str return nil, fmt.Errorf("failed to read .env file at '%s': %w", expandedPath, err) } - // If no keys specified, return all - if len(keys) == 0 { - kvs := make([]provider.KeyValue, 0, len(envMap)) - for k, v := range envMap { - kvs = append(kvs, provider.KeyValue{ - Key: k, - Value: v, - }) - } - return kvs, nil - } - - // Map keys according to configuration - kvs := make([]provider.KeyValue, 0) - for envKey, targetKey := range keys { - if value, exists := envMap[envKey]; exists { - if targetKey == "==" { - targetKey = envKey // Keep same name - } - kvs = append(kvs, provider.KeyValue{ - Key: targetKey, - Value: value, - }) - } + kvs := make([]provider.KeyValue, 0, len(envMap)) + for k, v := range envMap { + kvs = append(kvs, provider.KeyValue{ + Key: k, + Value: v, + }) } return kvs, nil } - diff --git a/internal/provider/dotenv/dotenv_test.go b/internal/provider/dotenv/dotenv_test.go index e472e6c..2d24803 100644 --- a/internal/provider/dotenv/dotenv_test.go +++ b/internal/provider/dotenv/dotenv_test.go @@ -60,7 +60,7 @@ func TestDotEnvProvider_Fetch_ConfigValidation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - _, err := provider.Fetch(ctx, "test-map", tt.config, nil) + _, err := provider.Fetch(ctx, "test-map", tt.config) if (err != nil) != tt.wantErr { t.Errorf("DotEnvProvider.Fetch() error = %v, wantErr %v", err, tt.wantErr) @@ -155,7 +155,7 @@ func TestDotEnvProvider_Fetch_WithValidFile(t *testing.T) { DATABASE_URL=postgres://localhost:5432/testdb SECRET_VALUE=my-secret-value ` - err := os.WriteFile(envFile, []byte(envContent), 0644) + err := os.WriteFile(envFile, []byte(envContent), 0o644) if err != nil { t.Fatalf("Failed to create test .env file: %v", err) } @@ -167,7 +167,7 @@ SECRET_VALUE=my-secret-value ctx := context.Background() // Test fetching all keys (empty keys map) - result, err := provider.Fetch(ctx, "test-map", config, nil) + result, err := provider.Fetch(ctx, "test-map", config) if err != nil { t.Fatalf("DotEnvProvider.Fetch() error = %v", err) } @@ -202,7 +202,7 @@ func TestDotEnvProvider_Fetch_WithKeyMapping(t *testing.T) { DATABASE_URL=postgres://localhost:5432/testdb OTHER_VALUE=should-not-appear ` - err := os.WriteFile(envFile, []byte(envContent), 0644) + err := os.WriteFile(envFile, []byte(envContent), 0o644) if err != nil { t.Fatalf("Failed to create test .env file: %v", err) } @@ -211,13 +211,8 @@ OTHER_VALUE=should-not-appear "path": envFile, } - keys := map[string]string{ - "API_KEY": "==", // Keep same name - "DATABASE_URL": "DB_URL", - } - ctx := context.Background() - result, err := provider.Fetch(ctx, "test-map", config, keys) + result, err := provider.Fetch(ctx, "test-map", config) if err != nil { t.Fatalf("DotEnvProvider.Fetch() error = %v", err) } @@ -228,8 +223,8 @@ OTHER_VALUE=should-not-appear // Verify mappings expectedKeys := map[string]string{ - "API_KEY": "test-api-key", - "DB_URL": "postgres://localhost:5432/testdb", + "API_KEY": "test-api-key", + "DATABASE_URL": "postgres://localhost:5432/testdb", } for _, kv := range result { @@ -275,4 +270,3 @@ func containsSubstring(s, substr string) bool { } return false } - diff --git a/internal/provider/gcsm/gcsm.go b/internal/provider/gcsm/gcsm.go index 23e1cc6..ad80ee1 100644 --- a/internal/provider/gcsm/gcsm.go +++ b/internal/provider/gcsm/gcsm.go @@ -44,7 +44,7 @@ func (p *GCSMProvider) Name() string { } // Fetch fetches secrets from Google Cloud Secret Manager -func (p *GCSMProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *GCSMProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -97,26 +97,9 @@ func (p *GCSMProvider) Fetch(ctx context.Context, mapID string, config map[strin // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - value := fmt.Sprintf("%v", v) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } @@ -169,4 +152,3 @@ func parseConfig(config map[string]interface{}) (*GCSMConfig, error) { return &cfg, nil } - diff --git a/internal/provider/gcsm/gcsm_test.go b/internal/provider/gcsm/gcsm_test.go index 25d2457..49d9473 100644 --- a/internal/provider/gcsm/gcsm_test.go +++ b/internal/provider/gcsm/gcsm_test.go @@ -73,7 +73,8 @@ func TestParseConfig(t *testing.T) { "project_id": "", "secret_id": "my-secret", }, - wantErr: false, // parseConfig doesn't validate, Fetch does + wantSecretID: "my-secret", + wantErr: false, // parseConfig doesn't validate, Fetch does }, { name: "config with missing project_id field", @@ -161,7 +162,7 @@ func TestGCSMProvider_Fetch_ConfigValidation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - _, err := provider.Fetch(ctx, "test-map", tt.config, nil) + _, err := provider.Fetch(ctx, "test-map", tt.config) if (err != nil) != tt.wantErr { t.Errorf("GCSMProvider.Fetch() error = %v, wantErr %v", err, tt.wantErr) @@ -279,4 +280,3 @@ func containsSubstring(s, substr string) bool { } return false } - diff --git a/internal/provider/infisical/infisical.go b/internal/provider/infisical/infisical.go index 5942424..8bc3ac3 100644 --- a/internal/provider/infisical/infisical.go +++ b/internal/provider/infisical/infisical.go @@ -43,7 +43,7 @@ func (p *InfisicalProvider) Name() string { } // Fetch fetches secrets from Infisical -func (p *InfisicalProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *InfisicalProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -108,26 +108,9 @@ func (p *InfisicalProvider) Fetch(ctx context.Context, mapID string, config map[ // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - value := fmt.Sprintf("%v", v) kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } diff --git a/internal/provider/interface.go b/internal/provider/interface.go index ed109b3..2e21751 100644 --- a/internal/provider/interface.go +++ b/internal/provider/interface.go @@ -19,7 +19,7 @@ type Provider interface { // Fetch fetches secrets from the provider based on the configuration // config contains provider-specific configuration fields (e.g., path, region, endpoint, etc.) - Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]KeyValue, error) + Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]KeyValue, error) } // Registry holds all registered providers @@ -47,4 +47,3 @@ func List() []string { } return kinds } - diff --git a/internal/provider/onepassword/onepassword.go b/internal/provider/onepassword/onepassword.go index 376d708..5540a9f 100644 --- a/internal/provider/onepassword/onepassword.go +++ b/internal/provider/onepassword/onepassword.go @@ -44,7 +44,7 @@ func (p *OnePasswordProvider) Name() string { } // Fetch fetches secrets from 1Password -func (p *OnePasswordProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *OnePasswordProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -97,13 +97,20 @@ func (p *OnePasswordProvider) Fetch(ctx context.Context, mapID string, config ma // Fetching the whole item secretData, err = p.extractWholeItem(item, cfg, parsedRef) } - if err != nil { return nil, err } - // Map keys according to configuration - return mapSecretKeys(secretData, keys), nil + kvs := make([]provider.KeyValue, 0) + for k, v := range secretData { + value := fmt.Sprintf("%v", v) + kvs = append(kvs, provider.KeyValue{ + Key: k, + Value: value, + }) + } + + return kvs, nil } // resolveAmbiguousRef resolves ambiguous references where part3 could be a field or section @@ -377,36 +384,6 @@ func (p *OnePasswordProvider) processSectionFields( return nil } -// mapSecretKeys maps secret data keys according to the provided key mapping -func mapSecretKeys(secretData map[string]interface{}, keys map[string]string) []provider.KeyValue { - kvs := make([]provider.KeyValue, 0) - for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - - value := fmt.Sprintf("%v", v) - kvs = append(kvs, provider.KeyValue{ - Key: targetKey, - Value: value, - }) - } - return kvs -} - // parsedRef represents a parsed 1Password reference type parsedRef struct { Vault string diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index dd8972c..a9502c9 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -11,4 +11,3 @@ func TestProviderInterface(t *testing.T) { t.Error("registry should not be nil") } } - diff --git a/internal/provider/vault/vault.go b/internal/provider/vault/vault.go index 25f161f..8e31a9f 100644 --- a/internal/provider/vault/vault.go +++ b/internal/provider/vault/vault.go @@ -40,7 +40,7 @@ func (p *VaultProvider) Name() string { } // Fetch fetches secrets from HashiCorp Vault -func (p *VaultProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}, keys map[string]string) ([]provider.KeyValue, error) { +func (p *VaultProvider) Fetch(ctx context.Context, mapID string, config map[string]interface{}) ([]provider.KeyValue, error) { // Convert map to strongly typed config struct cfg, err := parseConfig(config) if err != nil { @@ -102,23 +102,6 @@ func (p *VaultProvider) Fetch(ctx context.Context, mapID string, config map[stri // Map keys according to configuration kvs := make([]provider.KeyValue, 0) for k, v := range secretData { - targetKey := k - - // Check if there's a specific mapping - if mappedKey, exists := keys[k]; exists { - if mappedKey == "==" { - targetKey = k // Keep same name - } else { - targetKey = mappedKey - } - } else if len(keys) == 0 { - // No keys specified means map everything - targetKey = k - } else { - // Skip keys not in the mapping - continue - } - // Convert value to string var value string switch val := v.(type) { @@ -136,7 +119,7 @@ func (p *VaultProvider) Fetch(ctx context.Context, mapID string, config map[stri } kvs = append(kvs, provider.KeyValue{ - Key: targetKey, + Key: k, Value: value, }) } diff --git a/internal/provider/vault/vault_test.go b/internal/provider/vault/vault_test.go index 36b0d13..f86a919 100644 --- a/internal/provider/vault/vault_test.go +++ b/internal/provider/vault/vault_test.go @@ -157,7 +157,7 @@ func TestVaultProvider_Fetch_ConfigValidation(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ctx := context.Background() - _, err := provider.Fetch(ctx, "test-map", tt.config, nil) + _, err := provider.Fetch(ctx, "test-map", tt.config) if (err != nil) != tt.wantErr { t.Errorf("VaultProvider.Fetch() error = %v, wantErr %v", err, tt.wantErr) @@ -274,4 +274,3 @@ func containsSubstring(s, substr string) bool { } return false } - diff --git a/internal/secrets/collector.go b/internal/secrets/collector.go index cf8b5b3..24cf9b6 100644 --- a/internal/secrets/collector.go +++ b/internal/secrets/collector.go @@ -1,11 +1,13 @@ package secrets import ( + "bytes" "context" "fmt" "os" "regexp" "strings" + "text/template" "github.com/dirathea/sstart/internal/config" "github.com/dirathea/sstart/internal/provider" @@ -49,13 +51,20 @@ func (c *Collector) Collect(ctx context.Context, providerIDs []string) (map[stri expandedConfig := expandConfigTemplates(providerCfg.Config) // Fetch secrets from this provider's single source - kvs, err := prov.Fetch(ctx, providerCfg.ID, expandedConfig, providerCfg.Keys) + kvs, err := prov.Fetch(ctx, providerCfg.ID, expandedConfig) if err != nil { return nil, fmt.Errorf("failed to fetch from provider '%s': %w", providerID, err) } + tmplKvs, err := execTemplates(kvs, providerCfg.Templates) + if err != nil { + return nil, err + } + + mappingKvs := mapSecretKeys(kvs, providerCfg.Keys) + // Merge secrets (later providers override earlier ones) - for _, kv := range kvs { + for _, kv := range append(mappingKvs, tmplKvs...) { secrets[kv.Key] = kv.Value } } @@ -63,6 +72,67 @@ func (c *Collector) Collect(ctx context.Context, providerIDs []string) (map[stri return secrets, nil } +// execTemplates returns extra slice of KeyValue slice for given templates. +func execTemplates(in []provider.KeyValue, tmpls []*template.Template) ([]provider.KeyValue, error) { + if tmpls == nil { + return []provider.KeyValue{}, nil + } + out := make([]provider.KeyValue, 0) + for _, tmpl := range tmpls { + var buf bytes.Buffer + type tmplData struct { + Env map[string]string + } + values := make(map[string]string, len(out)) + for _, kv := range in { + values[kv.Key] = kv.Value + } + if err := tmpl.Execute(&buf, tmplData{Env: values}); err != nil { + return nil, fmt.Errorf("failed to execute template: %w", err) + } + envKey := tmpl.Name() + v := strings.TrimRight(buf.String(), "\n") + out = append(out, provider.KeyValue{ + Key: envKey, + Value: v, + }) + } + return out, nil +} + +// mapSecretKeys maps secret data keys according to the provided key mapping +func mapSecretKeys(in []provider.KeyValue, keys map[string]string) []provider.KeyValue { + if keys == nil { + return in + } + // Map keys according to configuration + out := make([]provider.KeyValue, 0) + for _, x := range in { + k, v := x.Key, x.Value + targetKey := k + + // Check if there's a specific mapping + if mappedKey, exists := keys[k]; exists { + if mappedKey == "==" { + targetKey = k // Keep same name + } else { + targetKey = mappedKey + } + } else if len(keys) == 0 { + // No keys specified means map everything + targetKey = k + } else { + // Skip keys not in the mapping + continue + } + out = append(out, provider.KeyValue{ + Key: targetKey, + Value: v, + }) + } + return out +} + // expandConfigTemplates expands template variables in config values // Supports {{ get_env(name="VAR", default="default") }} syntax func expandConfigTemplates(config map[string]interface{}) map[string]interface{} { diff --git a/internal/secrets/collector_test.go b/internal/secrets/collector_test.go new file mode 100644 index 0000000..d3d44c2 --- /dev/null +++ b/internal/secrets/collector_test.go @@ -0,0 +1,60 @@ +package secrets + +import ( + "reflect" + "testing" + + "github.com/dirathea/sstart/internal/provider" +) + +func Test_keyMapping(t *testing.T) { + type args struct { + in []provider.KeyValue + keys map[string]string + } + tests := []struct { + name string + args args + want []provider.KeyValue + }{ + { + name: "select", + args: args{ + in: []provider.KeyValue{ + {"API_KEY", "test-api-key"}, + {"DATABASE_URL", "postgres://localhost:5432/testdb"}, + {"OTHER_VALUE", "should-not-appear"}, + }, + keys: map[string]string{ + "API_KEY": "==", // Keep same name + }, + }, + want: []provider.KeyValue{ + {"API_KEY", "test-api-key"}, + }, + }, + { + name: "rename", + args: args{ + in: []provider.KeyValue{ + {"API_KEY", "test-api-key"}, + {"DATABASE_URL", "postgres://localhost:5432/testdb"}, + {"OTHER_VALUE", "should-not-appear"}, + }, + keys: map[string]string{ + "DATABASE_URL": "DB_URL", + }, + }, + want: []provider.KeyValue{ + {"DB_URL", "postgres://localhost:5432/testdb"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := keyMapping(tt.args.in, tt.args.keys); !reflect.DeepEqual(got, tt.want) { + t.Errorf("keyMapping() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/tests/end2end/aws_secretsmanager_test.go b/tests/end2end/aws_secretsmanager_test.go index 53b2739..0e284cb 100644 --- a/tests/end2end/aws_secretsmanager_test.go +++ b/tests/end2end/aws_secretsmanager_test.go @@ -50,7 +50,7 @@ providers: JWT_SECRET: == `, secretName, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -130,7 +130,7 @@ providers: endpoint: %s `, secretName, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/azure_keyvault_test.go b/tests/end2end/azure_keyvault_test.go index e7cfa3b..926e24c 100644 --- a/tests/end2end/azure_keyvault_test.go +++ b/tests/end2end/azure_keyvault_test.go @@ -49,7 +49,7 @@ providers: JWT_SECRET: == `, akvContainer.VaultURL, secretName) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -128,7 +128,7 @@ providers: secret_name: %s `, akvContainer.VaultURL, secretName) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/bitwarden_sm_test.go b/tests/end2end/bitwarden_sm_test.go index dda4021..84c7940 100644 --- a/tests/end2end/bitwarden_sm_test.go +++ b/tests/end2end/bitwarden_sm_test.go @@ -60,7 +60,7 @@ providers: project_id: %s `, serverURL, testSetup.OrganizationID, testSetup.ProjectID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/bitwarden_test.go b/tests/end2end/bitwarden_test.go index 78a7a19..28bd1b0 100644 --- a/tests/end2end/bitwarden_test.go +++ b/tests/end2end/bitwarden_test.go @@ -51,7 +51,7 @@ providers: DB_PASSWORD: BITWARDEN_DB_PASSWORD `, itemID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -127,7 +127,7 @@ providers: format: note `, itemID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -196,7 +196,7 @@ func TestE2E_Bitwarden_CLI_BothFormat(t *testing.T) { fields := map[string]string{ "API_KEY": "test-field-api-key-override", "DB_PASSWORD": "test-field-db-password-override", - "FIELD_ONLY": "field-only-value", + "FIELD_ONLY": "field-only-value", } itemID := SetupBitwardenItem(ctx, t, "sstart-test-both", 2, noteContent, fields, "", "") @@ -212,7 +212,7 @@ providers: format: both `, itemID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -234,10 +234,10 @@ providers: // Verify we got the expected secrets // Fields should take precedence over notes for duplicate keys expectedSecrets := map[string]string{ - "API_KEY": "test-field-api-key-override", // From fields (overrides note) + "API_KEY": "test-field-api-key-override", // From fields (overrides note) "DB_PASSWORD": "test-field-db-password-override", // From fields (overrides note) "JWT_SECRET": "test-note-jwt-token", // From notes (no field override) - "FIELD_ONLY": "field-only-value", // From fields only + "FIELD_ONLY": "field-only-value", // From fields only } for key, expectedValue := range expectedSecrets { diff --git a/tests/end2end/doppler_test.go b/tests/end2end/doppler_test.go index 900feaa..b97a8d0 100644 --- a/tests/end2end/doppler_test.go +++ b/tests/end2end/doppler_test.go @@ -65,7 +65,7 @@ providers: %s: == `, project, dopplerConfig, secretKey1, secretKey1, secretKey2, secretKey2, secretKey3) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -153,7 +153,7 @@ providers: config: %s `, project, dopplerConfig) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/format_test.go b/tests/end2end/format_test.go index 59785e9..c9d09ef 100644 --- a/tests/end2end/format_test.go +++ b/tests/end2end/format_test.go @@ -52,7 +52,7 @@ providers: JWT_SECRET: JWT_SECRET `, secretName, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -79,7 +79,7 @@ echo "SUCCESS: All JSON secrets parsed correctly" exit 0 ` - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } @@ -158,7 +158,7 @@ providers: KEY2_ONLY: KEY2_ONLY `, secretName1, localstack.Endpoint, secretName2, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -187,7 +187,7 @@ echo "SUCCESS: Multiple secrets override correctly" exit 0 ` - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } @@ -263,7 +263,7 @@ providers: endpoint: %s `, secretName, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -280,7 +280,7 @@ echo "SUCCESS: Non-JSON secret loaded correctly to AWS_NON_JSON_SECRET" exit 0 ` - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } @@ -387,7 +387,7 @@ providers: endpoint: %s `, jsonSecretName, localstack.Endpoint, nonJSONSecretName, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -415,7 +415,7 @@ echo "SUCCESS: Mixed JSON and non-JSON secrets handled correctly" exit 0 ` - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } diff --git a/tests/end2end/gcsm_test.go b/tests/end2end/gcsm_test.go index 1588478..5f47743 100644 --- a/tests/end2end/gcsm_test.go +++ b/tests/end2end/gcsm_test.go @@ -50,7 +50,7 @@ providers: foo: FOO `, projectID, secretID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -133,7 +133,7 @@ providers: secret_id: %s `, projectID, secretID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/infisical_test.go b/tests/end2end/infisical_test.go index b2d888a..57be6bb 100644 --- a/tests/end2end/infisical_test.go +++ b/tests/end2end/infisical_test.go @@ -68,7 +68,7 @@ providers: %s: == `, projectID, environment, secretPath, secretKey1, secretKey1, secretKey2, secretKey2, secretKey3) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -156,7 +156,7 @@ providers: path: %s `, projectID, environment, secretPath) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -238,7 +238,7 @@ providers: expand_secrets: false `, projectID, environment, secretPath) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/multi_provider_test.go b/tests/end2end/multi_provider_test.go index cea429a..bc994da 100644 --- a/tests/end2end/multi_provider_test.go +++ b/tests/end2end/multi_provider_test.go @@ -79,7 +79,7 @@ providers: VAULT_CONFIG: == `, secretName, localstack.Endpoint, vaultPath, vaultContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -191,7 +191,7 @@ providers: mount: secret `, secretName, localstack.Endpoint, vaultPath, vaultContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -309,7 +309,7 @@ providers: foo: FOO `, secretName, localstack.Endpoint, vaultPath, vaultContainer.Address, projectID, gcsmSecretID) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/onepassword_test.go b/tests/end2end/onepassword_test.go index 7099463..4539681 100644 --- a/tests/end2end/onepassword_test.go +++ b/tests/end2end/onepassword_test.go @@ -67,7 +67,7 @@ providers: ref: %s `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -137,7 +137,7 @@ providers: ref: %s `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -211,7 +211,7 @@ providers: ref: %s `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -292,7 +292,7 @@ providers: ref: %s `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -392,7 +392,7 @@ providers: use_section_prefix: true `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -487,7 +487,7 @@ providers: use_section_prefix: false `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -555,7 +555,7 @@ providers: use_section_prefix: false `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -646,7 +646,7 @@ providers: use_section_prefix: false `, onePasswordRef) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } diff --git a/tests/end2end/onepassword_testhelpers.go b/tests/end2end/onepassword_testhelpers.go index a430518..6341da3 100644 --- a/tests/end2end/onepassword_testhelpers.go +++ b/tests/end2end/onepassword_testhelpers.go @@ -73,9 +73,9 @@ func SetupOnePasswordItem(ctx context.Context, t *testing.T, client *onepassword // Add top-level fields (not in sections) for fieldTitle, fieldValue := range fields { itemFields = append(itemFields, onepassword.ItemField{ - ID: generateUniqueID(), - Title: fieldTitle, - Value: fieldValue, + ID: generateUniqueID(), + Title: fieldTitle, + Value: fieldValue, FieldType: onepassword.ItemFieldTypeText, }) } @@ -165,4 +165,3 @@ func GetOnePasswordItemByTitle(ctx context.Context, t *testing.T, client *onepas return &item } - diff --git a/tests/end2end/openbao_test.go b/tests/end2end/openbao_test.go index 7f430cc..2ba0fbc 100644 --- a/tests/end2end/openbao_test.go +++ b/tests/end2end/openbao_test.go @@ -51,7 +51,7 @@ providers: OPENBAO_CONFIG: == `, openbaoPath, openbaoContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -132,7 +132,7 @@ providers: mount: secret `, openbaoPath, openbaoContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -177,4 +177,3 @@ providers: t.Logf("Successfully collected %d secrets from OpenBao provider without key mappings", len(collectedSecrets)) } - diff --git a/tests/end2end/run_test.go b/tests/end2end/run_test.go index a74071f..21a9746 100644 --- a/tests/end2end/run_test.go +++ b/tests/end2end/run_test.go @@ -93,7 +93,7 @@ providers: RUN_VAULT_KEY: RUN_VAULT_KEY `, secretName, localstack.Endpoint, vaultPath, vaultContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -129,7 +129,7 @@ fi echo "SUCCESS: All secrets accessible and system env inherited" exit 0 ` - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } @@ -180,7 +180,7 @@ providers: RUN_VAULT_KEY: RUN_VAULT_KEY `, secretName, localstack.Endpoint, vaultPath, vaultContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -216,7 +216,7 @@ fi echo "SUCCESS: All secrets accessible and system env NOT inherited" exit 0 ` - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } @@ -284,7 +284,7 @@ providers: SIGNAL_TEST_KEY: SIGNAL_TEST_KEY `, secretName, localstack.Endpoint) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -312,7 +312,7 @@ while true; do done `, absSignalFile) - if err := os.WriteFile(testScript, []byte(scriptContent), 0755); err != nil { + if err := os.WriteFile(testScript, []byte(scriptContent), 0o755); err != nil { t.Fatalf("Failed to write test script: %v", err) } diff --git a/tests/end2end/vault_test.go b/tests/end2end/vault_test.go index 1313f40..8e03726 100644 --- a/tests/end2end/vault_test.go +++ b/tests/end2end/vault_test.go @@ -51,7 +51,7 @@ providers: VAULT_CONFIG: == `, vaultPath, vaultContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) } @@ -132,7 +132,7 @@ providers: mount: secret `, vaultPath, vaultContainer.Address) - if err := os.WriteFile(configFile, []byte(configYAML), 0644); err != nil { + if err := os.WriteFile(configFile, []byte(configYAML), 0o644); err != nil { t.Fatalf("Failed to write config file: %v", err) }