365 lines
No EOL
9.1 KiB
Go
365 lines
No EOL
9.1 KiB
Go
// ABOUTME: Configuration types for EdgeConnect apply command YAML parsing
|
|
// ABOUTME: Defines structs that match EdgeConnectConfig.yaml schema exactly
|
|
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
)
|
|
|
|
// EdgeConnectConfig represents the top-level configuration structure
|
|
type EdgeConnectConfig struct {
|
|
Kind string `yaml:"kind"`
|
|
Metadata Metadata `yaml:"metadata"`
|
|
Spec Spec `yaml:"spec"`
|
|
}
|
|
|
|
// Metadata contains configuration metadata
|
|
type Metadata struct {
|
|
Name string `yaml:"name"`
|
|
}
|
|
|
|
// Spec defines the application and infrastructure specification
|
|
type Spec struct {
|
|
K8sApp *K8sApp `yaml:"k8sApp,omitempty"`
|
|
DockerApp *DockerApp `yaml:"dockerApp,omitempty"`
|
|
InfraTemplate []InfraTemplate `yaml:"infraTemplate"`
|
|
Network *NetworkConfig `yaml:"network,omitempty"`
|
|
}
|
|
|
|
// K8sApp defines Kubernetes application configuration
|
|
type K8sApp struct {
|
|
AppName string `yaml:"appName"`
|
|
AppVersion string `yaml:"appVersion"`
|
|
ManifestFile string `yaml:"manifestFile"`
|
|
}
|
|
|
|
// DockerApp defines Docker application configuration
|
|
type DockerApp struct {
|
|
AppName string `yaml:"appName"`
|
|
AppVersion string `yaml:"appVersion"`
|
|
ManifestFile string `yaml:"manifestFile"`
|
|
Image string `yaml:"image"`
|
|
}
|
|
|
|
// InfraTemplate defines infrastructure deployment targets
|
|
type InfraTemplate struct {
|
|
Organization string `yaml:"organization"`
|
|
Region string `yaml:"region"`
|
|
CloudletOrg string `yaml:"cloudletOrg"`
|
|
CloudletName string `yaml:"cloudletName"`
|
|
FlavorName string `yaml:"flavorName"`
|
|
}
|
|
|
|
// NetworkConfig defines network configuration
|
|
type NetworkConfig struct {
|
|
OutboundConnections []OutboundConnection `yaml:"outboundConnections"`
|
|
}
|
|
|
|
// OutboundConnection defines an outbound network connection
|
|
type OutboundConnection struct {
|
|
Protocol string `yaml:"protocol"`
|
|
PortRangeMin int `yaml:"portRangeMin"`
|
|
PortRangeMax int `yaml:"portRangeMax"`
|
|
RemoteCIDR string `yaml:"remoteCIDR"`
|
|
}
|
|
|
|
// Validate performs comprehensive validation of the configuration
|
|
func (c *EdgeConnectConfig) Validate() error {
|
|
if c.Kind == "" {
|
|
return fmt.Errorf("kind is required")
|
|
}
|
|
|
|
if c.Kind != "edgeconnect-deployment" {
|
|
return fmt.Errorf("unsupported kind: %s, expected 'edgeconnect-deployment'", c.Kind)
|
|
}
|
|
|
|
if err := c.Metadata.Validate(); err != nil {
|
|
return fmt.Errorf("metadata validation failed: %w", err)
|
|
}
|
|
|
|
if err := c.Spec.Validate(); err != nil {
|
|
return fmt.Errorf("spec validation failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates metadata fields
|
|
func (m *Metadata) Validate() error {
|
|
if m.Name == "" {
|
|
return fmt.Errorf("metadata.name is required")
|
|
}
|
|
|
|
if strings.TrimSpace(m.Name) != m.Name {
|
|
return fmt.Errorf("metadata.name cannot have leading/trailing whitespace")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates spec configuration
|
|
func (s *Spec) Validate() error {
|
|
// Must have either k8sApp or dockerApp, but not both
|
|
if s.K8sApp == nil && s.DockerApp == nil {
|
|
return fmt.Errorf("spec must define either k8sApp or dockerApp")
|
|
}
|
|
|
|
if s.K8sApp != nil && s.DockerApp != nil {
|
|
return fmt.Errorf("spec cannot define both k8sApp and dockerApp")
|
|
}
|
|
|
|
// Validate app configuration
|
|
if s.K8sApp != nil {
|
|
if err := s.K8sApp.Validate(); err != nil {
|
|
return fmt.Errorf("k8sApp validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
if s.DockerApp != nil {
|
|
if err := s.DockerApp.Validate(); err != nil {
|
|
return fmt.Errorf("dockerApp validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Infrastructure template is required
|
|
if len(s.InfraTemplate) == 0 {
|
|
return fmt.Errorf("infraTemplate is required and must contain at least one target")
|
|
}
|
|
|
|
// Validate each infrastructure template
|
|
for i, infra := range s.InfraTemplate {
|
|
if err := infra.Validate(); err != nil {
|
|
return fmt.Errorf("infraTemplate[%d] validation failed: %w", i, err)
|
|
}
|
|
}
|
|
|
|
// Validate network configuration if present
|
|
if s.Network != nil {
|
|
if err := s.Network.Validate(); err != nil {
|
|
return fmt.Errorf("network validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates k8s app configuration
|
|
func (k *K8sApp) Validate() error {
|
|
if k.AppName == "" {
|
|
return fmt.Errorf("appName is required")
|
|
}
|
|
|
|
if k.AppVersion == "" {
|
|
return fmt.Errorf("appVersion is required")
|
|
}
|
|
|
|
if k.ManifestFile == "" {
|
|
return fmt.Errorf("manifestFile is required")
|
|
}
|
|
|
|
// Check if manifest file exists
|
|
if _, err := os.Stat(k.ManifestFile); os.IsNotExist(err) {
|
|
return fmt.Errorf("manifestFile does not exist: %s", k.ManifestFile)
|
|
}
|
|
|
|
// Validate app name format
|
|
if strings.TrimSpace(k.AppName) != k.AppName {
|
|
return fmt.Errorf("appName cannot have leading/trailing whitespace")
|
|
}
|
|
|
|
// Validate version format
|
|
if strings.TrimSpace(k.AppVersion) != k.AppVersion {
|
|
return fmt.Errorf("appVersion cannot have leading/trailing whitespace")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates docker app configuration
|
|
func (d *DockerApp) Validate() error {
|
|
if d.AppName == "" {
|
|
return fmt.Errorf("appName is required")
|
|
}
|
|
|
|
if d.AppVersion == "" {
|
|
return fmt.Errorf("appVersion is required")
|
|
}
|
|
|
|
if d.Image == "" {
|
|
return fmt.Errorf("image is required")
|
|
}
|
|
|
|
// Validate app name format
|
|
if strings.TrimSpace(d.AppName) != d.AppName {
|
|
return fmt.Errorf("appName cannot have leading/trailing whitespace")
|
|
}
|
|
|
|
// Validate version format
|
|
if strings.TrimSpace(d.AppVersion) != d.AppVersion {
|
|
return fmt.Errorf("appVersion cannot have leading/trailing whitespace")
|
|
}
|
|
|
|
// Check if manifest file exists if specified
|
|
if d.ManifestFile != "" {
|
|
if _, err := os.Stat(d.ManifestFile); os.IsNotExist(err) {
|
|
return fmt.Errorf("manifestFile does not exist: %s", d.ManifestFile)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates infrastructure template configuration
|
|
func (i *InfraTemplate) Validate() error {
|
|
if i.Organization == "" {
|
|
return fmt.Errorf("organization is required")
|
|
}
|
|
|
|
if i.Region == "" {
|
|
return fmt.Errorf("region is required")
|
|
}
|
|
|
|
if i.CloudletOrg == "" {
|
|
return fmt.Errorf("cloudletOrg is required")
|
|
}
|
|
|
|
if i.CloudletName == "" {
|
|
return fmt.Errorf("cloudletName is required")
|
|
}
|
|
|
|
if i.FlavorName == "" {
|
|
return fmt.Errorf("flavorName is required")
|
|
}
|
|
|
|
// Validate no leading/trailing whitespace
|
|
fields := map[string]string{
|
|
"organization": i.Organization,
|
|
"region": i.Region,
|
|
"cloudletOrg": i.CloudletOrg,
|
|
"cloudletName": i.CloudletName,
|
|
"flavorName": i.FlavorName,
|
|
}
|
|
|
|
for field, value := range fields {
|
|
if strings.TrimSpace(value) != value {
|
|
return fmt.Errorf("%s cannot have leading/trailing whitespace", field)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates network configuration
|
|
func (n *NetworkConfig) Validate() error {
|
|
if len(n.OutboundConnections) == 0 {
|
|
return fmt.Errorf("outboundConnections is required when network is specified")
|
|
}
|
|
|
|
for i, conn := range n.OutboundConnections {
|
|
if err := conn.Validate(); err != nil {
|
|
return fmt.Errorf("outboundConnections[%d] validation failed: %w", i, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Validate validates outbound connection configuration
|
|
func (o *OutboundConnection) Validate() error {
|
|
if o.Protocol == "" {
|
|
return fmt.Errorf("protocol is required")
|
|
}
|
|
|
|
validProtocols := map[string]bool{
|
|
"tcp": true,
|
|
"udp": true,
|
|
"icmp": true,
|
|
}
|
|
|
|
if !validProtocols[strings.ToLower(o.Protocol)] {
|
|
return fmt.Errorf("protocol must be one of: tcp, udp, icmp")
|
|
}
|
|
|
|
if o.PortRangeMin <= 0 || o.PortRangeMin > 65535 {
|
|
return fmt.Errorf("portRangeMin must be between 1 and 65535")
|
|
}
|
|
|
|
if o.PortRangeMax <= 0 || o.PortRangeMax > 65535 {
|
|
return fmt.Errorf("portRangeMax must be between 1 and 65535")
|
|
}
|
|
|
|
if o.PortRangeMin > o.PortRangeMax {
|
|
return fmt.Errorf("portRangeMin (%d) cannot be greater than portRangeMax (%d)", o.PortRangeMin, o.PortRangeMax)
|
|
}
|
|
|
|
if o.RemoteCIDR == "" {
|
|
return fmt.Errorf("remoteCIDR is required")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetManifestPath returns the absolute path to the manifest file
|
|
func (k *K8sApp) GetManifestPath(configDir string) string {
|
|
if filepath.IsAbs(k.ManifestFile) {
|
|
return k.ManifestFile
|
|
}
|
|
return filepath.Join(configDir, k.ManifestFile)
|
|
}
|
|
|
|
// GetManifestPath returns the absolute path to the manifest file
|
|
func (d *DockerApp) GetManifestPath(configDir string) string {
|
|
if d.ManifestFile == "" {
|
|
return ""
|
|
}
|
|
if filepath.IsAbs(d.ManifestFile) {
|
|
return d.ManifestFile
|
|
}
|
|
return filepath.Join(configDir, d.ManifestFile)
|
|
}
|
|
|
|
// GetAppName returns the application name from the active app type
|
|
func (s *Spec) GetAppName() string {
|
|
if s.K8sApp != nil {
|
|
return s.K8sApp.AppName
|
|
}
|
|
if s.DockerApp != nil {
|
|
return s.DockerApp.AppName
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetAppVersion returns the application version from the active app type
|
|
func (s *Spec) GetAppVersion() string {
|
|
if s.K8sApp != nil {
|
|
return s.K8sApp.AppVersion
|
|
}
|
|
if s.DockerApp != nil {
|
|
return s.DockerApp.AppVersion
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// GetManifestFile returns the manifest file path from the active app type
|
|
func (s *Spec) GetManifestFile() string {
|
|
if s.K8sApp != nil {
|
|
return s.K8sApp.ManifestFile
|
|
}
|
|
if s.DockerApp != nil {
|
|
return s.DockerApp.ManifestFile
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// IsK8sApp returns true if this is a Kubernetes application
|
|
func (s *Spec) IsK8sApp() bool {
|
|
return s.K8sApp != nil
|
|
}
|
|
|
|
// IsDockerApp returns true if this is a Docker application
|
|
func (s *Spec) IsDockerApp() bool {
|
|
return s.DockerApp != nil
|
|
} |