Declares GitCommit/BuildTime package vars (default "unknown", target of task 4.1's -ldflags -X injection), a -version flag checked before utils.PromptForTopDir()/InitLogger() (zero filesystem side effects), and extends the existing "Initialize Logger done!" startup log line with zap.String structured fields carrying both values, per design.md's explicit mandate. Live-verified end to end: -version output and the startup log's first line are byte-identical, with a real commit id (matching git rev-parse --short HEAD plus dirty suffix) and UTC timestamp -- the first full round-trip proof in this spec (earlier Go tasks could only verify via linked build-info metadata, since no consuming vars existed yet). Task 4.4/13 of build-time-versioning. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
137 lines
4.3 KiB
Go
137 lines
4.3 KiB
Go
package main
|
|
|
|
import (
|
|
_ "apiserver/docs"
|
|
"apiserver/backend"
|
|
"apiserver/backend/auth"
|
|
"apiserver/middleware"
|
|
"apiserver/router"
|
|
"apiserver/store"
|
|
"apiserver/utils"
|
|
logger "apiserver/utils"
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
|
|
"apiserver/controllers"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// GitCommit and BuildTime are injected at link time via
|
|
// `-ldflags "-X main.GitCommit=... -X main.BuildTime=..."` (see
|
|
// src/tools/go_build and src/frontend/Makefile's build-server target). The
|
|
// "unknown" defaults matter: a bare `go build`/`go run` that bypasses that
|
|
// ldflags injection must still report "unknown" rather than an empty
|
|
// string, matching this feature's unknown-fallback requirement (see
|
|
// xce_version_field() in src/lib/utils.c for the equivalent C-side
|
|
// convention).
|
|
var (
|
|
GitCommit = "unknown"
|
|
BuildTime = "unknown"
|
|
)
|
|
|
|
func main() {
|
|
gin.SetMode(gin.ReleaseMode)
|
|
gin.DefaultWriter = io.Discard
|
|
gin.DefaultErrorWriter = io.Discard
|
|
|
|
topDir := flag.String("d", "", "Top directory (default: current directory)")
|
|
versionFlag := flag.Bool("version", false, "Print version information and exit")
|
|
flag.Usage = func() {
|
|
fmt.Fprintln(os.Stderr, `
|
|
Usage: apiserver -d <top_directory>
|
|
Top directory (default: current directory)
|
|
`)
|
|
}
|
|
flag.Parse()
|
|
|
|
// Checked before utils.InitLogger() runs so -version has no logging
|
|
// side effects: it must only print and exit, matching controller's/
|
|
// scheduler's/worker's own --version contract (Requirement 2.1).
|
|
if *versionFlag {
|
|
fmt.Printf("xce-apiserver %s built %s\n", GitCommit, BuildTime)
|
|
return
|
|
}
|
|
|
|
utils.Global_TopDir = utils.PromptForTopDir(topDir)
|
|
|
|
if err := utils.InitLogger(); err != nil {
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("********** Initialize Logger done!",
|
|
zap.String("gitCommit", GitCommit),
|
|
zap.String("buildTime", BuildTime))
|
|
|
|
if err := utils.InitConf(); err != nil {
|
|
logger.Error("Failed to initialize configuration files", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("********** Initialize configuration files done!")
|
|
|
|
// Initialize in-memory stores
|
|
store.InitStores()
|
|
logger.Info("********** Initialize in-memory stores done!")
|
|
|
|
// Connect to the XCE controller backend for live data.
|
|
// StartNotificationListener must be called before RegisterToBackend so the
|
|
// handler is ready before any push events can arrive.
|
|
if utils.BackendYamlConfig != nil && utils.BackendYamlConfig.Address != "" {
|
|
handler := &backend.DefaultHandler{}
|
|
if err := backend.StartNotificationListener(handler); err != nil {
|
|
logger.Error("Failed to start notification listener", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
bcfg := &backend.BackendConfig{
|
|
Address: utils.BackendYamlConfig.Address,
|
|
Port: utils.BackendYamlConfig.Port,
|
|
From: utils.BackendYamlConfig.From,
|
|
Key: utils.BackendYamlConfig.Key,
|
|
Peers: utils.BackendYamlConfig.Peers,
|
|
}
|
|
if err := backend.RegisterToBackend(bcfg); err != nil {
|
|
logger.Error("Failed to connect to backend", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("********** Backend connection registered!")
|
|
backend.StartHeartbeat(context.Background())
|
|
} else {
|
|
logger.Info("********** Backend not configured — running in stub mode")
|
|
}
|
|
|
|
// Load RBAC config
|
|
rbacPath := utils.Global_TopDir + "/config/rbac.yaml"
|
|
if err := controllers.LoadRBACConfig(rbacPath); err != nil {
|
|
logger.Error("Failed to load RBAC config", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("********** Load RBAC config done!")
|
|
|
|
// Initialize auth provider
|
|
authConfigPath := utils.Global_TopDir + "/config/auth.yaml"
|
|
if err := auth.InitAuthProvider(authConfigPath); err != nil {
|
|
logger.Error("Failed to initialize auth provider", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
logger.Info("********** Initialize auth provider done! (" + auth.GlobalProvider.Name() + ")")
|
|
|
|
// Pre-populate user-role store from auth provider so permissions work
|
|
// immediately after restart without requiring users to re-login.
|
|
if seeder, ok := auth.GlobalProvider.(auth.UserRoleSeeder); ok {
|
|
for _, username := range seeder.AllUsers() {
|
|
for _, role := range auth.GlobalProvider.GetUserRoles(username) {
|
|
store.AddRoleForUser(username, role)
|
|
}
|
|
}
|
|
logger.Info("********** Pre-populated user-role store from auth provider done!")
|
|
}
|
|
|
|
r := router.Router()
|
|
if err := middleware.StartServer(r); err != nil {
|
|
logger.Error("Server failed to start:", zap.Error(err))
|
|
os.Exit(1)
|
|
}
|
|
}
|