iop/apps/control-plane/cmd/control-plane/main.go
toki dd488f1e56 refactor(runtime): 포트 표준화와 브리지 경계를 반영한다
워크스페이스 기본 포트와 검증 문서를 실제 런타임 기본값에 맞추고, 브리지 경계에서 adapter config 타입 정보를 보존해야 이후 원격 smoke와 경계 안정화 작업이 같은 계약을 사용할 수 있다.
2026-06-07 17:51:54 +09:00

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
}