Initial Commit

This commit is contained in:
2025-11-10 16:44:59 -05:00
commit 6decdb603c
13 changed files with 978 additions and 0 deletions

221
credentials/credentials.go Normal file
View File

@@ -0,0 +1,221 @@
package credentials
import (
"fmt"
"io/ioutil"
"os"
"gopkg.in/yaml.v3"
)
// ChainConfig represents a single chain configuration
type ChainConfig struct {
Name string `yaml:"name"`
PublicId string `yaml:"publicId"`
AuthKeyId string `yaml:"authKeyId"`
AuthKey string `yaml:"authKey"`
Endpoint string `yaml:"endpoint"`
}
// Config represents the entire configuration file structure
type Config struct {
Default string `yaml:"default"`
Chains []ChainConfig `yaml:"chains"`
node *yaml.Node // Store the original node for preserving formatting
}
// LoadConfig reads and parses a YAML configuration file
func LoadConfig(filePath string) (*Config, error) {
// Expand the file path (handles ~/, ./, and environment variables)
expandedPath, err := expandPath(filePath)
if err != nil {
return nil, fmt.Errorf("failed to expand path %s: %w", filePath, err)
}
// Read the file
data, err := ioutil.ReadFile(expandedPath)
if err != nil {
return nil, fmt.Errorf("failed to read config file %s: %w", expandedPath, err)
}
// Parse YAML with Node to preserve formatting
var node yaml.Node
if err := yaml.Unmarshal(data, &node); err != nil {
return nil, fmt.Errorf("failed to parse YAML config: %w", err)
}
var config Config
if err := node.Decode(&config); err != nil {
return nil, fmt.Errorf("failed to decode YAML config: %w", err)
}
// Store the node for later use when saving
config.node = &node
return &config, nil
}
// LoadConfigFromString parses YAML configuration from a string
func LoadConfigFromString(yamlContent string) (*Config, error) {
var node yaml.Node
if err := yaml.Unmarshal([]byte(yamlContent), &node); err != nil {
return nil, fmt.Errorf("failed to parse YAML config: %w", err)
}
var config Config
if err := node.Decode(&config); err != nil {
return nil, fmt.Errorf("failed to decode YAML config: %w", err)
}
config.node = &node
return &config, nil
}
// GetDefaultChain returns the chain configuration for the default publicId
func (c *Config) GetDefaultChain() *ChainConfig {
for i := range c.Chains {
if c.Chains[i].PublicId == c.Default {
return &c.Chains[i]
}
}
return nil
}
// GetChainByPublicId returns the chain configuration for the specified publicId
func (c *Config) GetChainByPublicId(publicId string) *ChainConfig {
for i := range c.Chains {
if c.Chains[i].PublicId == publicId {
return &c.Chains[i]
}
}
return nil
}
// ListChains returns all chain names
func (c *Config) ListChains() []string {
names := make([]string, len(c.Chains))
for i, chain := range c.Chains {
names[i] = chain.Name
}
return names
}
// AddChain adds a chain configuration to the configuration
func (c *Config) AddChain(chain *ChainConfig) {
c.Chains = append(c.Chains, *chain)
}
// SetDefault sets the default chain publicId
func (c *Config) SetDefault(publicId string) {
c.Default = publicId
}
// DeleteChain deletes a chain configuration from the configuration
func (c *Config) DeleteChain(publicId string) error {
if publicId == c.Default {
return fmt.Errorf("cannot delete default chain")
}
for i, chain := range c.Chains {
if chain.PublicId == publicId {
c.Chains = append(c.Chains[:i], c.Chains[i+1:]...)
return nil
}
}
return nil
}
// SaveConfig writes the configuration to a YAML file
func (c *Config) SaveConfig(filePath string) error {
expandedPath, err := expandPath(filePath)
if err != nil {
return fmt.Errorf("failed to expand path %s: %w", filePath, err)
}
var data []byte
if c.node != nil {
// Update the node with current config values while preserving formatting
if err := c.node.Encode(c); err != nil {
return fmt.Errorf("failed to encode config to node: %w", err)
}
// Ensure newlines between chain entries
c.ensureChainSeparation()
// Marshal the node to preserve comments and formatting
data, err = yaml.Marshal(c.node)
if err != nil {
return fmt.Errorf("failed to marshal config to YAML: %w", err)
}
} else {
// Fallback to regular marshaling if no node is available
data, err = yaml.Marshal(c)
if err != nil {
return fmt.Errorf("failed to marshal config to YAML: %w", err)
}
}
if err := ioutil.WriteFile(expandedPath, data, 0644); err != nil {
return fmt.Errorf("failed to write config file %s: %w", expandedPath, err)
}
return nil
}
// ensureChainSeparation adds blank lines between chain entries in the YAML node
func (c *Config) ensureChainSeparation() {
if c.node == nil || len(c.node.Content) == 0 {
return
}
// Navigate to the document node, then the mapping node
docNode := c.node
if docNode.Kind == yaml.DocumentNode && len(docNode.Content) > 0 {
docNode = docNode.Content[0]
}
if docNode.Kind != yaml.MappingNode {
return
}
// Find the "chains" key in the mapping
for i := 0; i < len(docNode.Content); i += 2 {
if i+1 >= len(docNode.Content) {
break
}
keyNode := docNode.Content[i]
valueNode := docNode.Content[i+1]
if keyNode.Value == "chains" && valueNode.Kind == yaml.SequenceNode {
// Add HeadComment (newline before) to each chain entry except the first
for j := 1; j < len(valueNode.Content); j++ {
chainNode := valueNode.Content[j]
if chainNode.HeadComment == "" {
chainNode.HeadComment = "\n"
}
}
break
}
}
}
// expandPath is a simple path expansion function (you can replace this with your utils.ExpandPath)
func expandPath(path string) (string, error) {
// Expand environment variables
path = os.ExpandEnv(path)
// Handle home directory
if len(path) > 0 && path[0] == '~' {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
if len(path) == 1 {
path = home
} else if path[1] == '/' {
path = home + path[1:]
}
}
return path, nil
}