From be43de0a8937db8acaa1a44e6f1517928702ae7f Mon Sep 17 00:00:00 2001 From: Frederic CORDIER Date: Mon, 20 Oct 2025 14:39:28 +0200 Subject: [PATCH 1/4] feat: create cvm service, and config --- .gitignore | 1 + apploader/internal/config/config.go | 43 ++++++ apploader/internal/cvm/cvm.go | 206 +++++++++------------------- apploader/main.go | 9 ++ apploader/pkg/command/command.go | 44 ++++++ 5 files changed, 159 insertions(+), 144 deletions(-) create mode 100644 .gitignore create mode 100644 apploader/internal/config/config.go create mode 100644 apploader/pkg/command/command.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bf700a5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +apploader/conf/local.yml \ No newline at end of file diff --git a/apploader/internal/config/config.go b/apploader/internal/config/config.go new file mode 100644 index 0000000..76d18af --- /dev/null +++ b/apploader/internal/config/config.go @@ -0,0 +1,43 @@ +package config + +import "os" + +// Config holds application configuration +type Config struct { + Server ServerConfig + Cvm CvmConfig +} + +// ServerConfig holds server configuration +type ServerConfig struct { + Port string +} + +// CvmConfig holds cvm configuration +type CvmConfig struct { + ConfigPath string + SupervisorPath string + SupervisorTemplatePath string +} + +// Load loads configuration from environment variables +func Load() *Config { + return &Config{ + Server: ServerConfig{ + Port: getEnv("PORT", ":9090"), + }, + Cvm: CvmConfig{ + ConfigPath: getEnv("CVM_CONFIG_PATH", "/workplace/cvm-app/config/app.yml"), + SupervisorPath: getEnv("SUPERVISOR_PATH", "/workplace/supervisord/apploader"), + SupervisorTemplatePath: getEnv("SUPERVISOR_TEMPLATE_PATH", "conf/supervisord.ini.template"), + }, + } +} + +// getEnv gets an environment variable with a fallback default value +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} diff --git a/apploader/internal/cvm/cvm.go b/apploader/internal/cvm/cvm.go index 8b39cfa..3836704 100644 --- a/apploader/internal/cvm/cvm.go +++ b/apploader/internal/cvm/cvm.go @@ -1,153 +1,94 @@ package cvm -/* -#include -#include -#include -*/ -import "C" import ( + "apploader/internal/config" "apploader/internal/secret" + "apploader/pkg/command" "apploader/pkg/conversion" "apploader/pkg/file" "fmt" "log" "os" - "os/exec" "path" "strings" - "syscall" "text/template" - "time" - "unsafe" "gopkg.in/yaml.v3" ) const ( - JOB = "job" - SERVER = "server" - DockerApp = "dockerApp" - APP = "app" + JOB = "job" + SERVER = "server" ) -const ( - SUPERVISOR_PATH = "/workplace/supervisord/apploader" -) - -func DoJob(ca *TaskInfo) error { - if ca.Type != JOB { - return fmt.Errorf("this task is not a job") - } - - envs := make([]string, 0) - for k, v := range secret.Secret { - envs = append(envs, fmt.Sprintf("%s=%s", k, v)) - } - - if ca.Env != nil { - userEnv, ok := ca.Env.(map[string]interface{}) - if !ok { - return fmt.Errorf("user env format error") - } - - for k, v := range userEnv { - envs = append(envs, fmt.Sprintf("%s=%s", k, v)) - } - } - - log.Printf("entrypoint is %s", ca.Entrypoint) - return RunCommand(ca.Entrypoint, envs, ca.Args...) - +type CvmService interface { + Start() } -func RunCommand(name string, envs []string, arg ...string) error { - cmd := exec.Command(name, arg...) +type cvmService struct { + config *config.CvmConfig + cvmApp *CvmApp +} - cmd.Dir = path.Dir(name) - // - stdout, err := cmd.StdoutPipe() - cmd.Stderr = cmd.Stdout - cmd.Env = envs +// NewCvmService creates a new cvm service +func NewCvmService(config *config.CvmConfig) (CvmService, error) { + service := &cvmService{config: config} + cvmApp, err := service.loadConfig() if err != nil { - return err - } - 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 - } - } - if err = cmd.Wait(); err != nil { - if ex, ok := err.(*exec.ExitError); ok { - cmdExitStatus := ex.Sys().(syscall.WaitStatus).ExitStatus() - log.Println(cmdExitStatus) - } - log.Println(err) - return err + return nil, err } - return nil + service.cvmApp = cvmApp + return service, nil } -func Execv(main string, args ...string) { - workdir := path.Dir(main) - if err := os.Chdir(workdir); err != nil { - log.Fatalf("change to work dir failed, error: %s\n", err.Error()) - } +// Start starts the cvm service +func (s *cvmService) Start() { + s.startTask(s.cvmApp.CvmAssistants) + s.startTask(s.cvmApp.AppInfo) +} - sizeOfArgs_C := len(args) + 2 // +2 for [0]=main [1:end]=args [end]=nil - Args_C := make([]*C.char, sizeOfArgs_C) - Args_C[0] = C.CString(main) - defer C.free(unsafe.Pointer(Args_C[0])) - for i, arg := range args { - Args_C[i+1] = C.CString(arg) - defer C.free(unsafe.Pointer(Args_C[i+1])) +// loadConfig loads the cvm app config +func (s *cvmService) loadConfig() (*CvmApp, error) { + appfile, err := os.ReadFile(s.config.ConfigPath) + if err != nil { + return nil, fmt.Errorf("read %s failed, error: %s", s.config.ConfigPath, err.Error()) } - Args_C[sizeOfArgs_C-1] = nil - re := C.execv(C.CString(main), (**C.char)(unsafe.Pointer(&Args_C[0]))) - if re != 0 { - log.Fatalf("execv %s failed, code is %d\n", main, re) + cvmApp := new(CvmApp) + err = yaml.Unmarshal(appfile, &cvmApp) + if err != nil { + return nil, fmt.Errorf("unmarshal %s failed, error: %s", s.config.ConfigPath, err.Error()) } - return + return cvmApp, nil } -func ExecvDockerApp(ca *TaskInfo) { - if ca.Type != DockerApp { - log.Fatalf("task is not a docker app") +func (s *cvmService) DoJob(taskInfo *TaskInfo) error { + if taskInfo.Type != JOB { + return fmt.Errorf("this task is not a job") } + envs := make([]string, 0) for k, v := range secret.Secret { - err := os.Setenv(k, v) - if err != nil { - log.Fatalf("set secret env failed, error: %s\n", err.Error()) - } + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } - if ca.Env != nil { - userEnv, ok := ca.Env.(map[string]interface{}) + if taskInfo.Env != nil { + userEnv, ok := taskInfo.Env.(map[string]interface{}) if !ok { - log.Fatalf("user env format error") + return fmt.Errorf("user env format error") } for k, v := range userEnv { - err := os.Setenv(k, v.(string)) - if err != nil { - log.Fatalf("set app env failed, error: %s\n", err.Error()) - } + envs = append(envs, fmt.Sprintf("%s=%s", k, v)) } } - Execv(ca.Entrypoint, ca.Args...) + log.Printf("entrypoint is %s", taskInfo.Entrypoint) + return command.RunCommand(taskInfo.Entrypoint, envs, taskInfo.Args...) + } -func CreateSevers(ca *TaskInfo) error { - if ca.Type != SERVER { +func (s *cvmService) CreateSevers(taskInfo *TaskInfo) error { + if taskInfo.Type != SERVER { return fmt.Errorf("task is not a server") } @@ -158,8 +99,8 @@ func CreateSevers(ca *TaskInfo) error { // envs = append(envs, fmt.Sprintf("%s=%s", k, v)) //} - if ca.Env != nil { - userEnv, ok := ca.Env.(map[string]interface{}) + if taskInfo.Env != nil { + userEnv, ok := taskInfo.Env.(map[string]interface{}) if !ok { return fmt.Errorf("user env format error") } @@ -170,60 +111,41 @@ func CreateSevers(ca *TaskInfo) error { } sConf := new(SupervisorConf) - sConf.Name = ca.Name - sConf.Command = ca.Entrypoint + " " + strings.Join(ca.Args, " ") - sConf.Workplace = path.Dir(ca.Entrypoint) + sConf.Name = taskInfo.Name + sConf.Command = taskInfo.Entrypoint + " " + strings.Join(taskInfo.Args, " ") + sConf.Workplace = path.Dir(taskInfo.Entrypoint) sConf.Environment = strings.Join(envs, ",") - sConf.Priority = ca.Priority + sConf.Priority = taskInfo.Priority - tmpl, err := template.ParseFiles("conf/supervisord.ini.template") + tmpl, err := template.ParseFiles(s.config.SupervisorTemplatePath) if err != nil { - return fmt.Errorf("parse supervisord template file failed, error: %s\n", err.Error()) + return fmt.Errorf("parse %s failed, error: %s", s.config.SupervisorTemplatePath, err.Error()) } - supervisordINIPath := path.Join(SUPERVISOR_PATH, fmt.Sprintf("%s.ini", ca.Name)) + supervisordINIPath := path.Join(s.config.SupervisorPath, fmt.Sprintf("%s.ini", taskInfo.Name)) if file.IsFile(supervisordINIPath) { os.RemoveAll(supervisordINIPath) } f, err := os.Create(supervisordINIPath) if err != nil { - return fmt.Errorf("create supervisord.ini failed, error: %s\n", err.Error()) + return fmt.Errorf("create %s failed, error: %s", supervisordINIPath, err.Error()) } defer f.Close() if err := tmpl.Execute(f, sConf); err != nil { - return fmt.Errorf("file the supervisord.ini failed, error: %s\n", err.Error()) + return fmt.Errorf("fill the %s failed, error: %s", supervisordINIPath, err.Error()) } return nil } -func Start() { - appfile, err := os.ReadFile("conf/app.yml") - if err != nil { - log.Fatalf("read app.yml failed, error: %s\n", err.Error()) - } - cvmApp := new(CvmApp) - err = yaml.Unmarshal(appfile, &cvmApp) - if err != nil { - log.Fatalf("unmarshal app.yml failed, error: %s\n", err.Error()) - } - time.Sleep(5 * time.Second) - - log.Println("do all the job over") - - startTask(cvmApp.CvmAssistants) - - startTask(cvmApp.AppInfo) -} - -func startTask(tasks []*TaskInfo) { +func (s *cvmService) startTask(tasks []*TaskInfo) { for i, t := range tasks { switch t.Type { case JOB: log.Printf("begin to do job %s\n", t.Name) - err := DoJob(t) + err := s.DoJob(t) if err != nil { log.Fatalf("do job %s failed, error: %s\n", t.Name, err.Error()) } @@ -231,23 +153,19 @@ func startTask(tasks []*TaskInfo) { case SERVER: log.Printf("begin to deploy server %s\n", t.Name) t.Priority = i + 2 - err := CreateSevers(t) + err := s.CreateSevers(t) if err != nil { log.Fatalf("deploy server %s failed, error: %s\n", t.Name, err) } - err = RunCommand("supervisorctl", nil, "update") + err = command.RunCommand("supervisorctl", nil, "update") if err != nil { log.Fatalf("update supervisor conf failed, error: %s", err.Error()) } - err = RunCommand("supervisorctl", nil, "start", 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.Printf("end to deply server %s\n", t.Name) - case DockerApp: - log.Printf("begin to run docker app %s\n", t.Name) - log.Printf("docker app will only run the first app\n") - ExecvDockerApp(t) default: log.Fatalf("task type: %s does not support", t.Type) } diff --git a/apploader/main.go b/apploader/main.go index 26d8454..5189c17 100644 --- a/apploader/main.go +++ b/apploader/main.go @@ -1,15 +1,24 @@ package main import ( + "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.NewCvmService(&cfg.Cvm) + if err != nil { + log.Fatalf("failed to create cvm service: %v", err) + } go secret.StartSecretServer() + time.Sleep(2 * time.Second) // wait for secret server to start cvm.Start() os.Exit(0) } diff --git a/apploader/pkg/command/command.go b/apploader/pkg/command/command.go new file mode 100644 index 0000000..2809be8 --- /dev/null +++ b/apploader/pkg/command/command.go @@ -0,0 +1,44 @@ +package command + +import ( + "fmt" + "log" + "os/exec" + "path" + "syscall" +) + +// RunCommand runs a command +func RunCommand(name string, envs []string, arg ...string) error { + cmd := exec.Command(name, arg...) + + cmd.Dir = path.Dir(name) + // + stdout, err := cmd.StdoutPipe() + cmd.Stderr = cmd.Stdout + cmd.Env = envs + if err != nil { + return err + } + 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 + } + } + if err = cmd.Wait(); err != nil { + if ex, ok := err.(*exec.ExitError); ok { + cmdExitStatus := ex.Sys().(syscall.WaitStatus).ExitStatus() + log.Println(cmdExitStatus) + } + log.Println(err) + return err + } + return nil +} From 800a318c18841bc052f245b9fa4b8ff2a44dfa96 Mon Sep 17 00:00:00 2001 From: Frederic CORDIER Date: Mon, 20 Oct 2025 14:42:07 +0200 Subject: [PATCH 2/4] fix: missing endline --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bf700a5..cf17bb3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -apploader/conf/local.yml \ No newline at end of file +apploader/conf/local.yml From 117065b2005542078d39c9c8cac770eebd8829c0 Mon Sep 17 00:00:00 2001 From: Frederic CORDIER Date: Mon, 20 Oct 2025 14:57:46 +0200 Subject: [PATCH 3/4] chore: remove empty comments --- apploader/pkg/command/command.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/apploader/pkg/command/command.go b/apploader/pkg/command/command.go index 2809be8..ccc440e 100644 --- a/apploader/pkg/command/command.go +++ b/apploader/pkg/command/command.go @@ -13,7 +13,7 @@ func RunCommand(name string, envs []string, arg ...string) error { cmd := exec.Command(name, arg...) cmd.Dir = path.Dir(name) - // + stdout, err := cmd.StdoutPipe() cmd.Stderr = cmd.Stdout cmd.Env = envs @@ -23,7 +23,6 @@ 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) From 0ffbfb269e02203179239656044a1a4c6761b2e2 Mon Sep 17 00:00:00 2001 From: Frederic CORDIER Date: Mon, 20 Oct 2025 17:47:28 +0200 Subject: [PATCH 4/4] refactor: rename CvmService to CvmBootManager and CvmApp to CvmBootSequence --- apploader/internal/cvm/cvm.go | 54 ++++++++++++++++----------------- apploader/internal/cvm/types.go | 4 +-- apploader/main.go | 4 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/apploader/internal/cvm/cvm.go b/apploader/internal/cvm/cvm.go index 3836704..deb0cd4 100644 --- a/apploader/internal/cvm/cvm.go +++ b/apploader/internal/cvm/cvm.go @@ -21,47 +21,47 @@ const ( SERVER = "server" ) -type CvmService interface { +type CvmBootManager interface { Start() } -type cvmService struct { - config *config.CvmConfig - cvmApp *CvmApp +type cvmBootManager struct { + config *config.CvmConfig + cvmBootSequence *CvmBootSequence } -// NewCvmService creates a new cvm service -func NewCvmService(config *config.CvmConfig) (CvmService, error) { - service := &cvmService{config: config} - cvmApp, err := service.loadConfig() +// NewCvmBootManager creates a new cvm service +func NewCvmBootManager(config *config.CvmConfig) (CvmBootManager, error) { + service := &cvmBootManager{config: config} + cvmBootSequence, err := service.loadConfig() if err != nil { return nil, err } - service.cvmApp = cvmApp + service.cvmBootSequence = cvmBootSequence return service, nil } // Start starts the cvm service -func (s *cvmService) Start() { - s.startTask(s.cvmApp.CvmAssistants) - s.startTask(s.cvmApp.AppInfo) +func (s *cvmBootManager) Start() { + s.startTask(s.cvmBootSequence.CvmAssistants) + s.startTask(s.cvmBootSequence.AppInfo) } // loadConfig loads the cvm app config -func (s *cvmService) loadConfig() (*CvmApp, error) { - appfile, err := os.ReadFile(s.config.ConfigPath) +func (cbm *cvmBootManager) loadConfig() (*CvmBootSequence, error) { + appfile, err := os.ReadFile(cbm.config.ConfigPath) if err != nil { - return nil, fmt.Errorf("read %s failed, error: %s", s.config.ConfigPath, err.Error()) + return nil, fmt.Errorf("read %s failed, error: %s", cbm.config.ConfigPath, err.Error()) } - cvmApp := new(CvmApp) - err = yaml.Unmarshal(appfile, &cvmApp) + cvmBootSequence := new(CvmBootSequence) + err = yaml.Unmarshal(appfile, &cvmBootSequence) if err != nil { - return nil, fmt.Errorf("unmarshal %s failed, error: %s", s.config.ConfigPath, err.Error()) + return nil, fmt.Errorf("unmarshal %s failed, error: %s", cbm.config.ConfigPath, err.Error()) } - return cvmApp, nil + return cvmBootSequence, nil } -func (s *cvmService) DoJob(taskInfo *TaskInfo) error { +func (cbm *cvmBootManager) DoJob(taskInfo *TaskInfo) error { if taskInfo.Type != JOB { return fmt.Errorf("this task is not a job") } @@ -87,7 +87,7 @@ func (s *cvmService) DoJob(taskInfo *TaskInfo) error { } -func (s *cvmService) CreateSevers(taskInfo *TaskInfo) error { +func (cbm *cvmBootManager) CreateSevers(taskInfo *TaskInfo) error { if taskInfo.Type != SERVER { return fmt.Errorf("task is not a server") } @@ -117,12 +117,12 @@ func (s *cvmService) CreateSevers(taskInfo *TaskInfo) error { sConf.Environment = strings.Join(envs, ",") sConf.Priority = taskInfo.Priority - tmpl, err := template.ParseFiles(s.config.SupervisorTemplatePath) + tmpl, err := template.ParseFiles(cbm.config.SupervisorTemplatePath) if err != nil { - return fmt.Errorf("parse %s failed, error: %s", s.config.SupervisorTemplatePath, err.Error()) + return fmt.Errorf("parse %s failed, error: %s", cbm.config.SupervisorTemplatePath, err.Error()) } - supervisordINIPath := path.Join(s.config.SupervisorPath, fmt.Sprintf("%s.ini", taskInfo.Name)) + supervisordINIPath := path.Join(cbm.config.SupervisorPath, fmt.Sprintf("%s.ini", taskInfo.Name)) if file.IsFile(supervisordINIPath) { os.RemoveAll(supervisordINIPath) } @@ -140,12 +140,12 @@ func (s *cvmService) CreateSevers(taskInfo *TaskInfo) error { return nil } -func (s *cvmService) startTask(tasks []*TaskInfo) { +func (cbm *cvmBootManager) startTask(tasks []*TaskInfo) { for i, t := range tasks { switch t.Type { case JOB: log.Printf("begin to do job %s\n", t.Name) - err := s.DoJob(t) + err := cbm.DoJob(t) if err != nil { log.Fatalf("do job %s failed, error: %s\n", t.Name, err.Error()) } @@ -153,7 +153,7 @@ func (s *cvmService) startTask(tasks []*TaskInfo) { case SERVER: log.Printf("begin to deploy server %s\n", t.Name) t.Priority = i + 2 - err := s.CreateSevers(t) + err := cbm.CreateSevers(t) if err != nil { log.Fatalf("deploy server %s failed, error: %s\n", t.Name, err) } diff --git a/apploader/internal/cvm/types.go b/apploader/internal/cvm/types.go index 1757131..f273038 100644 --- a/apploader/internal/cvm/types.go +++ b/apploader/internal/cvm/types.go @@ -1,7 +1,7 @@ package cvm -// CvmApp is the main application configuration -type CvmApp struct { +// CvmBootSequence is the main application configuration +type CvmBootSequence struct { Kind string `yaml:"kind"` AppInfo []*TaskInfo `yaml:"app"` CvmAssistants []*TaskInfo `yaml:"csvAssistants"` diff --git a/apploader/main.go b/apploader/main.go index 5189c17..063633a 100644 --- a/apploader/main.go +++ b/apploader/main.go @@ -13,9 +13,9 @@ func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) cfg := config.Load() - cvm, err := cvm.NewCvmService(&cfg.Cvm) + cvm, err := cvm.NewCvmBootManager(&cfg.Cvm) if err != nil { - log.Fatalf("failed to create cvm service: %v", err) + log.Fatalf("failed to create cvm boot manager: %v", err) } go secret.StartSecretServer() time.Sleep(2 * time.Second) // wait for secret server to start