Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions apploader/internal/application/application.go
Original file line number Diff line number Diff line change
@@ -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)
}
29 changes: 16 additions & 13 deletions apploader/internal/cvm/cvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
}

Expand All @@ -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...)

}
Expand Down Expand Up @@ -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)
}
}
}
31 changes: 31 additions & 0 deletions apploader/internal/secret/handler.go
Original file line number Diff line number Diff line change
@@ -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",
})
}
34 changes: 0 additions & 34 deletions apploader/internal/secret/secret.go

This file was deleted.

36 changes: 36 additions & 0 deletions apploader/internal/secret/service.go
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 2 additions & 12 deletions apploader/main.go
Original file line number Diff line number Diff line change
@@ -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)
}
19 changes: 9 additions & 10 deletions apploader/pkg/command/command.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package command

import (
"fmt"
"bufio"
"log"
"os/exec"
"path"
Expand All @@ -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
Expand Down