diff --git a/apploader/internal/application/application.go b/apploader/internal/application/application.go new file mode 100644 index 0000000..16bce3a --- /dev/null +++ b/apploader/internal/application/application.go @@ -0,0 +1,111 @@ +package application + +import ( + "apploader/internal/config" + "apploader/internal/cvm" + "apploader/internal/secret" + "context" + "log" + "net" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" +) + +// Application orchestrates CVM boot sequence, secrets, and HTTP server. +type Application struct { + config *config.Config + cvmBootManager cvm.CvmBootManager + secretService secret.SecretService + server *http.Server +} + +// New creates a new application +func New(cfg *config.Config) *Application { + secretService := secret.NewSecretService() + cvmBootManager, err := cvm.NewCvmBootManager(&cfg.Cvm, secretService) + if err != nil { + log.Fatalf("Failed to create CVM boot manager: %v", err) + } + return &Application{ + config: cfg, + secretService: secretService, + cvmBootManager: cvmBootManager, + } +} + +// Start starts the application +func (app *Application) Start() { + // Channel to signal when API is ready + apiReady := make(chan struct{}) + go app.startHTTP(apiReady) + <-apiReady + log.Printf("API started successfully") + + // Channel to signal when CVM boot sequence is done + cvmDone := make(chan bool, 1) + + // Start CVM boot sequence in background + go func() { + log.Printf("Starting CVM boot sequence") + app.cvmBootManager.Start() + log.Printf("CVM boot sequence completed successfully") + cvmDone <- true + }() + + // Setup graceful shutdown + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + + select { + case <-cvmDone: + log.Printf("CVM boot sequence completed. Application will exit.") + case <-quit: + log.Printf("Shutting down application...") + } + + // Graceful shutdown of HTTP server + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + if err := app.server.Shutdown(ctx); err != nil { + log.Printf("Server forced to shutdown: %v", err) + } +} + +// StartHTTP starts the HTTP server +func (app *Application) startHTTP(ready chan<- struct{}) { + log.Printf("Starting HTTP server on port %s", app.config.Server.Port) + + router := gin.New() + // Custom logging middleware with log.Printf format + router.Use(func(c *gin.Context) { + c.Next() + log.Printf("API Request: %s %s %d", c.Request.Method, c.Request.URL.Path, c.Writer.Status()) + }) + + router.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok"}) + }) + secret.NewSecretHandler(app.secretService).RegisterHandler(router) + + app.server = &http.Server{ + Addr: app.config.Server.Port, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + IdleTimeout: 10 * time.Second, + + Handler: router, + } + listener, err := net.Listen("tcp", app.config.Server.Port) + if err != nil { + log.Fatalf("Failed to start server: %v", err) + } + close(ready) + + app.server.Serve(listener) +} diff --git a/apploader/internal/cvm/cvm.go b/apploader/internal/cvm/cvm.go index 2778bed..83fa365 100644 --- a/apploader/internal/cvm/cvm.go +++ b/apploader/internal/cvm/cvm.go @@ -28,11 +28,12 @@ type CvmBootManager interface { type cvmBootManager struct { config *config.CvmConfig cvmBootSequence *CvmBootSequence + secretService secret.SecretService } // NewCvmBootManager creates a new cvm service -func NewCvmBootManager(config *config.CvmConfig) (CvmBootManager, error) { - service := &cvmBootManager{config: config} +func NewCvmBootManager(config *config.CvmConfig, secretService secret.SecretService) (CvmBootManager, error) { + service := &cvmBootManager{config: config, secretService: secretService} cvmBootSequence, err := service.loadConfig() if err != nil { return nil, err @@ -71,7 +72,7 @@ func (cbm *cvmBootManager) executeTask(taskInfo *TaskInfo) error { } envs := make([]string, 0) - for k, v := range secret.Secret { + for k, v := range cbm.secretService.GetAllSecrets() { envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } @@ -86,7 +87,6 @@ func (cbm *cvmBootManager) executeTask(taskInfo *TaskInfo) error { } } - log.Printf("entrypoint is %s", taskInfo.Entrypoint) return command.RunCommand(taskInfo.Entrypoint, envs, taskInfo.Args...) } @@ -148,30 +148,33 @@ func (cbm *cvmBootManager) processTasks(tasks []*TaskInfo) { for i, t := range tasks { switch t.Type { case JOB: - log.Printf("begin to do job %s\n", t.Name) + log.Printf("Executing job: %s (entrypoint: %s)", t.Name, t.Entrypoint) err := cbm.executeTask(t) if err != nil { - log.Fatalf("do job %s failed, error: %s\n", t.Name, err.Error()) + log.Fatalf("Failed to execute job %s: %v", t.Name, err) } - log.Printf("end to do job %s\n", t.Name) + log.Printf("Job completed: %s", t.Name) case SERVER: - log.Printf("begin to deploy server %s\n", t.Name) + log.Printf("Deploying service: %s (entrypoint: %s)", t.Name, t.Entrypoint) t.Priority = i + 2 err := cbm.deployService(t) if err != nil { - log.Fatalf("deploy server %s failed, error: %s\n", t.Name, err) + log.Fatalf("Failed to deploy service %s: %v", t.Name, err) } + log.Printf("Service deployed: %s", t.Name) + log.Printf("Updating supervisor configuration for service: %s", t.Name) err = command.RunCommand("supervisorctl", nil, "update") if err != nil { - log.Fatalf("update supervisor conf failed, error: %s", err.Error()) + log.Fatalf("Failed to update supervisor configuration: %v", err) } + log.Printf("Starting service: %s with supervisor configuration", t.Name) err = command.RunCommand("supervisorctl", nil, "start", t.Name) if err != nil { - log.Fatalf("start %s failed, error: %s", t.Name, err.Error()) + log.Fatalf("Failed to start service %s: %v", t.Name, err) } - log.Printf("end to deply server %s\n", t.Name) + log.Printf("Service started: %s", t.Name) default: - log.Fatalf("task type: %s does not support", t.Type) + log.Fatalf("Task type: %s is not supported", t.Type) } } } diff --git a/apploader/internal/secret/handler.go b/apploader/internal/secret/handler.go new file mode 100644 index 0000000..eff70e7 --- /dev/null +++ b/apploader/internal/secret/handler.go @@ -0,0 +1,31 @@ +package secret + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// SecretHandler is the handler for the secret service +type SecretHandler struct { + service SecretService +} + +// NewSecretHandler creates a new secret handler +func NewSecretHandler(service SecretService) *SecretHandler { + return &SecretHandler{service: service} +} + +// RegisterHandler registers the secret handler +func (h *SecretHandler) RegisterHandler(router *gin.Engine) { + router.POST("/secret", h.saveSecret) +} + +// SaveSecret saves a secret +func (h *SecretHandler) saveSecret(c *gin.Context) { + h.service.SaveSecret(c.PostForm("key"), c.PostForm("value")) + c.JSON(http.StatusOK, gin.H{ + "code": 200, + "message": "update secret successful", + }) +} diff --git a/apploader/internal/secret/secret.go b/apploader/internal/secret/secret.go deleted file mode 100644 index 380cb4a..0000000 --- a/apploader/internal/secret/secret.go +++ /dev/null @@ -1,34 +0,0 @@ -package secret - -import ( - "log" - "net/http" - - "github.com/gin-gonic/gin" -) - -type JsonResult struct { - Code int `json:"code"` - Msg string `json:"msg"` -} - -var Secret map[string]string - -func StartSecretServer() { - Secret = make(map[string]string) - gin.SetMode(gin.ReleaseMode) - r := gin.Default() - r.POST("/secret", func(c *gin.Context) { - k := c.PostForm("key") - v := c.PostForm("value") // - - Secret[k] = v - c.JSON(http.StatusOK, gin.H{ - "code": 200, - "message": "update secret successful", - }) - }) - - log.Printf("secret server start successful") - r.Run(":9090") // listen and serve on 0.0.0.0:8080 -} diff --git a/apploader/internal/secret/service.go b/apploader/internal/secret/service.go new file mode 100644 index 0000000..18f2ae6 --- /dev/null +++ b/apploader/internal/secret/service.go @@ -0,0 +1,36 @@ +package secret + +import "sync" + +// SecretService is the interface for the secret service +type SecretService interface { + SaveSecret(key string, value string) + GetAllSecrets() map[string]string +} + +// secretService is the implementation of the SecretService interface +type secretService struct { + secrets map[string]string + secretsMutex sync.RWMutex +} + +// NewSecretService creates a new secret service +func NewSecretService() SecretService { + return &secretService{ + secrets: make(map[string]string), + } +} + +// SaveSecret saves a secret +func (s *secretService) SaveSecret(key string, value string) { + s.secretsMutex.Lock() + defer s.secretsMutex.Unlock() + s.secrets[key] = value +} + +// GetAllSecrets gets all secrets +func (s *secretService) GetAllSecrets() map[string]string { + s.secretsMutex.RLock() + defer s.secretsMutex.RUnlock() + return s.secrets +} diff --git a/apploader/main.go b/apploader/main.go index 063633a..9b851df 100644 --- a/apploader/main.go +++ b/apploader/main.go @@ -1,24 +1,14 @@ package main import ( + "apploader/internal/application" "apploader/internal/config" - "apploader/internal/cvm" - "apploader/internal/secret" "log" "os" - "time" ) func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) - - cfg := config.Load() - cvm, err := cvm.NewCvmBootManager(&cfg.Cvm) - if err != nil { - log.Fatalf("failed to create cvm boot manager: %v", err) - } - go secret.StartSecretServer() - time.Sleep(2 * time.Second) // wait for secret server to start - cvm.Start() + application.New(config.Load()).Start() os.Exit(0) } diff --git a/apploader/pkg/command/command.go b/apploader/pkg/command/command.go index ccc440e..f5ce9d0 100644 --- a/apploader/pkg/command/command.go +++ b/apploader/pkg/command/command.go @@ -1,7 +1,7 @@ package command import ( - "fmt" + "bufio" "log" "os/exec" "path" @@ -23,20 +23,19 @@ func RunCommand(name string, envs []string, arg ...string) error { if err = cmd.Start(); err != nil { return err } - for { - tmp := make([]byte, 128) - _, err := stdout.Read(tmp) - fmt.Print(string(tmp)) - if err != nil { - break - } + scanner := bufio.NewScanner(stdout) + for scanner.Scan() { + log.Printf("Command output: %s", scanner.Text()) + } + if err := scanner.Err(); err != nil { + log.Printf("Error reading command output: %v", err) } if err = cmd.Wait(); err != nil { if ex, ok := err.(*exec.ExitError); ok { cmdExitStatus := ex.Sys().(syscall.WaitStatus).ExitStatus() - log.Println(cmdExitStatus) + log.Printf("Command execution failed with exit status: %d", cmdExitStatus) } - log.Println(err) + log.Printf("Command execution failed: %v", err) return err } return nil