174 lines
4.9 KiB
Go
174 lines
4.9 KiB
Go
// ABOUTME: YAML configuration parser for EdgeConnect apply command with comprehensive validation
|
|
// ABOUTME: Handles parsing and validation of EdgeConnectConfig files with detailed error messages
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
// Parser defines the interface for configuration parsing
|
|
type Parser interface {
|
|
ParseFile(filename string) (*EdgeConnectConfig, string, error)
|
|
ParseBytes(data []byte) (*EdgeConnectConfig, error)
|
|
Validate(config *EdgeConnectConfig) error
|
|
}
|
|
|
|
// ConfigParser implements the Parser interface
|
|
type ConfigParser struct{}
|
|
|
|
// NewParser creates a new configuration parser
|
|
func NewParser() Parser {
|
|
return &ConfigParser{}
|
|
}
|
|
|
|
// ParseFile parses an EdgeConnectConfig from a YAML file
|
|
func (p *ConfigParser) ParseFile(filename string) (*EdgeConnectConfig, string, error) {
|
|
if filename == "" {
|
|
return nil, "", fmt.Errorf("filename cannot be empty")
|
|
}
|
|
|
|
// Check if file exists
|
|
if _, err := os.Stat(filename); os.IsNotExist(err) {
|
|
return nil, "", fmt.Errorf("configuration file does not exist: %s", filename)
|
|
}
|
|
|
|
// Read file contents
|
|
data, err := os.ReadFile(filename)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to read configuration file %s: %w", filename, err)
|
|
}
|
|
|
|
// Parse YAML without validation first
|
|
config, err := p.parseYAMLOnly(data)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to parse configuration file %s: %w", filename, err)
|
|
}
|
|
|
|
// Resolve relative paths relative to config file directory
|
|
configDir := filepath.Dir(filename)
|
|
if err := p.resolveRelativePaths(config, configDir); err != nil {
|
|
return nil, "", fmt.Errorf("failed to resolve paths in %s: %w", filename, err)
|
|
}
|
|
|
|
// Now validate with resolved paths
|
|
if err := p.Validate(config); err != nil {
|
|
return nil, "", fmt.Errorf("configuration validation failed in %s: %w", filename, err)
|
|
}
|
|
|
|
manifest, err := p.readManifestFiles(config)
|
|
if err != nil {
|
|
return nil, "", fmt.Errorf("failed to read manifest files: %w", err)
|
|
}
|
|
|
|
return config, manifest, nil
|
|
}
|
|
|
|
// parseYAMLOnly parses YAML without validation
|
|
func (p *ConfigParser) parseYAMLOnly(data []byte) (*EdgeConnectConfig, error) {
|
|
if len(data) == 0 {
|
|
return nil, fmt.Errorf("configuration data cannot be empty")
|
|
}
|
|
|
|
var config EdgeConnectConfig
|
|
|
|
// Parse YAML with strict mode
|
|
decoder := yaml.NewDecoder(nil)
|
|
decoder.KnownFields(true) // Fail on unknown fields
|
|
|
|
if err := yaml.Unmarshal(data, &config); err != nil {
|
|
return nil, fmt.Errorf("YAML parsing failed: %w", err)
|
|
}
|
|
|
|
return &config, nil
|
|
}
|
|
|
|
// ParseBytes parses an EdgeConnectConfig from YAML bytes
|
|
func (p *ConfigParser) ParseBytes(data []byte) (*EdgeConnectConfig, error) {
|
|
// Parse YAML only
|
|
config, err := p.parseYAMLOnly(data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Validate the parsed configuration
|
|
if err := p.Validate(config); err != nil {
|
|
return nil, fmt.Errorf("configuration validation failed: %w", err)
|
|
}
|
|
|
|
return config, nil
|
|
}
|
|
|
|
// Validate performs comprehensive validation of the configuration
|
|
func (p *ConfigParser) Validate(config *EdgeConnectConfig) error {
|
|
if config == nil {
|
|
return fmt.Errorf("configuration cannot be nil")
|
|
}
|
|
|
|
return config.Validate()
|
|
}
|
|
|
|
// resolveRelativePaths converts relative paths to absolute paths based on config directory
|
|
func (p *ConfigParser) resolveRelativePaths(config *EdgeConnectConfig, configDir string) error {
|
|
if config.Spec.K8sApp != nil {
|
|
resolved := config.Spec.K8sApp.GetManifestPath(configDir)
|
|
config.Spec.K8sApp.ManifestFile = resolved
|
|
}
|
|
|
|
if config.Spec.DockerApp != nil && config.Spec.DockerApp.ManifestFile != "" {
|
|
resolved := config.Spec.DockerApp.GetManifestPath(configDir)
|
|
config.Spec.DockerApp.ManifestFile = resolved
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ValidateManifestFiles performs additional validation on manifest files
|
|
func (p *ConfigParser) readManifestFiles(config *EdgeConnectConfig) (string, error) {
|
|
var manifestFile string
|
|
|
|
if config.Spec.K8sApp != nil {
|
|
manifestFile = config.Spec.K8sApp.ManifestFile
|
|
} else if config.Spec.DockerApp != nil {
|
|
manifestFile = config.Spec.DockerApp.ManifestFile
|
|
}
|
|
|
|
if manifestFile == "" {
|
|
return "", nil
|
|
}
|
|
|
|
if manifestFile != "" {
|
|
if err := p.validateManifestFile(manifestFile); err != nil {
|
|
return "", fmt.Errorf("manifest file validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Try to read the file to ensure it's accessible
|
|
content, err := os.ReadFile(manifestFile)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot read manifest file %s: %w", manifestFile, err)
|
|
}
|
|
|
|
return string(content), nil
|
|
}
|
|
|
|
// validateManifestFile checks if the manifest file is valid and readable
|
|
func (p *ConfigParser) validateManifestFile(filename string) error {
|
|
info, err := os.Stat(filename)
|
|
if err != nil {
|
|
return fmt.Errorf("cannot access manifest file %s: %w", filename, err)
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return fmt.Errorf("manifest file cannot be a directory: %s", filename)
|
|
}
|
|
|
|
if info.Size() == 0 {
|
|
return fmt.Errorf("manifest file cannot be empty: %s", filename)
|
|
}
|
|
|
|
return nil
|
|
}
|