워크스페이스 기본 포트와 검증 문서를 실제 런타임 기본값에 맞추고, 브리지 경계에서 adapter config 타입 정보를 보존해야 이후 원격 smoke와 경계 안정화 작업이 같은 계약을 사용할 수 있다.
157 lines
4 KiB
Go
157 lines
4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"go.uber.org/zap"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"iop/packages/go/observability"
|
|
)
|
|
|
|
var cfgFile string
|
|
|
|
type controlPlaneConfig struct {
|
|
Server struct {
|
|
Listen string `yaml:"listen"`
|
|
WireListen string `yaml:"wire_listen"`
|
|
EdgeWireListen string `yaml:"edge_wire_listen"`
|
|
} `yaml:"server"`
|
|
Database struct {
|
|
URL string `yaml:"url"`
|
|
} `yaml:"database"`
|
|
Redis struct {
|
|
URL string `yaml:"url"`
|
|
KeyPrefix string `yaml:"key_prefix"`
|
|
} `yaml:"redis"`
|
|
Logging struct {
|
|
Level string `yaml:"level"`
|
|
Pretty bool `yaml:"pretty"`
|
|
} `yaml:"logging"`
|
|
}
|
|
|
|
func main() {
|
|
if err := rootCmd().Execute(); err != nil {
|
|
fmt.Fprintln(os.Stderr, err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func rootCmd() *cobra.Command {
|
|
root := &cobra.Command{
|
|
Use: "control-plane",
|
|
Short: "IOP Control Plane - multi-edge management server",
|
|
Long: `control-plane will manage Edge connections, observe Edge state,
|
|
adjust Edge configuration, deliver commands to Edge, and receive Edge events.
|
|
|
|
It controls the system through Edge, not by connecting to or scheduling Node
|
|
directly. The main IOP communication layer is protobuf-socket; net/http is kept
|
|
for health, readiness, and bootstrap endpoints.`,
|
|
}
|
|
root.PersistentFlags().StringVarP(&cfgFile, "config", "c", "configs/control-plane.yaml", "config file path")
|
|
root.AddCommand(serveCmd())
|
|
return root
|
|
}
|
|
|
|
func serveCmd() *cobra.Command {
|
|
return &cobra.Command{
|
|
Use: "serve",
|
|
Short: "Start the control-plane server",
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
cfg, err := loadConfig(cfgFile)
|
|
if err != nil {
|
|
return fmt.Errorf("load config: %w", err)
|
|
}
|
|
logger, err := observability.NewLogger(cfg.Logging.Level, cfg.Logging.Pretty)
|
|
if err != nil {
|
|
return fmt.Errorf("logger: %w", err)
|
|
}
|
|
defer func() { _ = logger.Sync() }()
|
|
|
|
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
|
defer stop()
|
|
return run(ctx, cfg, logger)
|
|
},
|
|
}
|
|
}
|
|
|
|
func defaultConfig() controlPlaneConfig {
|
|
var cfg controlPlaneConfig
|
|
cfg.Server.Listen = "0.0.0.0:18000"
|
|
cfg.Server.WireListen = "0.0.0.0:19080"
|
|
cfg.Server.EdgeWireListen = "0.0.0.0:19081"
|
|
cfg.Database.URL = ""
|
|
cfg.Redis.URL = ""
|
|
cfg.Redis.KeyPrefix = "iop:control-plane:"
|
|
cfg.Logging.Level = "info"
|
|
return cfg
|
|
}
|
|
|
|
func loadConfig(path string) (controlPlaneConfig, error) {
|
|
cfg := defaultConfig()
|
|
if path != "" {
|
|
b, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
if err := yaml.Unmarshal(b, &cfg); err != nil {
|
|
return cfg, err
|
|
}
|
|
}
|
|
applyEnvOverrides(&cfg)
|
|
return cfg, nil
|
|
}
|
|
|
|
func applyEnvOverrides(cfg *controlPlaneConfig) {
|
|
if listen := os.Getenv("IOP_LISTEN"); listen != "" {
|
|
cfg.Server.Listen = listen
|
|
}
|
|
if wireListen := os.Getenv("IOP_WIRE_LISTEN"); wireListen != "" {
|
|
cfg.Server.WireListen = wireListen
|
|
}
|
|
if edgeWireListen := os.Getenv("IOP_EDGE_WIRE_LISTEN"); edgeWireListen != "" {
|
|
cfg.Server.EdgeWireListen = edgeWireListen
|
|
}
|
|
if databaseURL := os.Getenv("IOP_DATABASE_URL"); databaseURL != "" {
|
|
cfg.Database.URL = databaseURL
|
|
}
|
|
if redisURL := os.Getenv("IOP_REDIS_URL"); redisURL != "" {
|
|
cfg.Redis.URL = redisURL
|
|
}
|
|
if redisKeyPrefix := os.Getenv("IOP_REDIS_KEY_PREFIX"); redisKeyPrefix != "" {
|
|
cfg.Redis.KeyPrefix = redisKeyPrefix
|
|
}
|
|
}
|
|
|
|
func databaseLogFields(rawURL string) []zap.Field {
|
|
return serviceURLLogFields(rawURL)
|
|
}
|
|
|
|
func redisLogFields(rawURL string, keyPrefix string) []zap.Field {
|
|
fields := serviceURLLogFields(rawURL)
|
|
if keyPrefix != "" {
|
|
fields = append(fields, zap.String("key_prefix", keyPrefix))
|
|
}
|
|
return fields
|
|
}
|
|
|
|
func serviceURLLogFields(rawURL string) []zap.Field {
|
|
fields := []zap.Field{zap.Bool("configured", true)}
|
|
parsed, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return fields
|
|
}
|
|
if parsed.Host != "" {
|
|
fields = append(fields, zap.String("host", parsed.Host))
|
|
}
|
|
if dbName := strings.TrimPrefix(parsed.Path, "/"); dbName != "" {
|
|
fields = append(fields, zap.String("database", dbName))
|
|
}
|
|
return fields
|
|
}
|