Files
kmanage/cmd/helper_config.go

216 lines
5.4 KiB
Go

package cmd
import (
"fmt"
"os"
"path/filepath"
"strings"
"kmanage/pkg/secret"
"github.com/spf13/viper"
"gopkg.in/yaml.v3"
)
// AppConfig represents the configuration stored in ~/.kmanage.yaml.
type AppConfig struct {
KomodoURL string `yaml:"komodo_url" mapstructure:"komodo_url"`
KomodoKey string `yaml:"komodo_key,omitempty" mapstructure:"komodo_key"` // Keyring fallback only
KomodoSecret string `yaml:"komodo_secret,omitempty" mapstructure:"komodo_secret"` // Keyring fallback only
DefaultGitAccount string `yaml:"default_git_account" mapstructure:"default_git_account"`
GitAccounts map[string]GitAccount `yaml:"git_accounts" mapstructure:"git_accounts"`
}
// GitAccount holds metadata for a configured Git provider account.
type GitAccount struct {
Type string `yaml:"type" mapstructure:"type"` // gitea, forgejo, github
URL string `yaml:"url" mapstructure:"url"`
Username string `yaml:"username,omitempty" mapstructure:"username,omitempty"`
Token string `yaml:"token,omitempty" mapstructure:"token"` // Keyring fallback only
}
func getConfigFilePath() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("unable to determine user home directory: %w", err)
}
return filepath.Join(home, ".kmanage.yaml"), nil
}
// LoadAppConfig loads configuration from ~/.kmanage.yaml.
func LoadAppConfig() (*AppConfig, error) {
configPath, err := getConfigFilePath()
if err != nil {
return nil, err
}
cfg := &AppConfig{
GitAccounts: make(map[string]GitAccount),
}
data, err := os.ReadFile(configPath)
if err == nil {
_ = yaml.Unmarshal(data, cfg)
}
if cfg.GitAccounts == nil {
cfg.GitAccounts = make(map[string]GitAccount)
}
// Automatic migration of legacy single-account configuration (git_url / git_token)
oldGitURL := viper.GetString("git_url")
if oldGitURL != "" && len(cfg.GitAccounts) == 0 {
accountName := "default"
accountType := "gitea"
if strings.Contains(oldGitURL, "github.com") {
accountType = "github"
accountName = "github"
} else if strings.Contains(oldGitURL, "git.") {
accountName = "gitea"
}
oldToken := viper.GetString("git_token")
if oldToken != "" {
_ = secret.SetSecret("git:"+accountName+":token", oldToken)
}
cfg.GitAccounts[accountName] = GitAccount{
Type: accountType,
URL: oldGitURL,
}
if cfg.DefaultGitAccount == "" {
cfg.DefaultGitAccount = accountName
}
_ = saveAppConfig(cfg)
}
return cfg, nil
}
func saveAppConfig(cfg *AppConfig) error {
configPath, err := getConfigFilePath()
if err != nil {
return err
}
data, err := yaml.Marshal(cfg)
if err != nil {
return fmt.Errorf("failed to serialize configuration: %w", err)
}
if err := os.WriteFile(configPath, data, 0600); err != nil {
return fmt.Errorf("failed to write configuration file: %w", err)
}
_ = viper.ReadInConfig()
return nil
}
// SaveKomodoConfig saves the Komodo URL in config and securely stores credentials in the OS keyring.
func SaveKomodoConfig(url, key, secretVal string) error {
cfg, err := LoadAppConfig()
if err != nil {
return err
}
cfg.KomodoURL = strings.TrimRight(url, "/")
cfg.KomodoKey = ""
cfg.KomodoSecret = ""
// Store in OS Keyring
if err := secret.SetSecret("komodo:key", key); err != nil {
cfg.KomodoKey = key
}
if err := secret.SetSecret("komodo:secret", secretVal); err != nil {
cfg.KomodoSecret = secretVal
}
return saveAppConfig(cfg)
}
// SaveGitAccount saves a Git account configuration and securely stores its token in the OS keyring.
func SaveGitAccount(name, accountType, url, token, username string) error {
name = strings.TrimSpace(name)
if name == "" {
return fmt.Errorf("account name cannot be empty")
}
cfg, err := LoadAppConfig()
if err != nil {
return err
}
accountType = strings.ToLower(strings.TrimSpace(accountType))
if accountType == "" {
if strings.Contains(url, "github.com") {
accountType = "github"
} else {
accountType = "gitea"
}
}
url = strings.TrimRight(url, "/")
if url == "" && accountType == "github" {
url = "https://api.github.com"
}
acc := GitAccount{
Type: accountType,
URL: url,
Username: username,
}
keyringKey := "git:" + name + ":token"
if err := secret.SetSecret(keyringKey, token); err != nil {
acc.Token = token
}
cfg.GitAccounts[name] = acc
if cfg.DefaultGitAccount == "" || len(cfg.GitAccounts) == 1 {
cfg.DefaultGitAccount = name
}
return saveAppConfig(cfg)
}
// SetDefaultGitAccount sets the active default Git account.
func SetDefaultGitAccount(name string) error {
cfg, err := LoadAppConfig()
if err != nil {
return err
}
if _, exists := cfg.GitAccounts[name]; !exists {
return fmt.Errorf("account '%s' not found. View configured accounts with 'kmanage git accounts'", name)
}
cfg.DefaultGitAccount = name
return saveAppConfig(cfg)
}
// DeleteGitAccount deletes a Git account and removes its secret from the OS keyring.
func DeleteGitAccount(name string) error {
cfg, err := LoadAppConfig()
if err != nil {
return err
}
if _, exists := cfg.GitAccounts[name]; !exists {
return fmt.Errorf("account '%s' not found", name)
}
delete(cfg.GitAccounts, name)
_ = secret.DeleteSecret("git:" + name + ":token")
if cfg.DefaultGitAccount == name {
cfg.DefaultGitAccount = ""
for k := range cfg.GitAccounts {
cfg.DefaultGitAccount = k
break
}
}
return saveAppConfig(cfg)
}