package space_worker import ( "bytes" _ "embed" "fmt" "os" "os/exec" "path/filepath" "playbookctl/internal/types" "playbookctl/internal/utils" "playbookctl/internal/utils/logger" "slices" "strings" ) var ( //go:embed static/README.MD staticReadme []byte ) type SpaceWorker struct { log *logger.Logger // Рабочая папка workDir string AnsibleBin string AnsibleVerbose uint8 } func NewSpaceWorker(verbose logger.LogVerbose, workDir string) *SpaceWorker { return &SpaceWorker{ log: &logger.Logger{Verbose: verbose}, workDir: workDir, } } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func (app *SpaceWorker) CreateRole(name string) error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Debug(fmt.Sprintf("role name: %s", name)) rolePath := filepath.Join(app.workDir, "roles", name) app.log.Debug(fmt.Sprintf("rolePath: %s", rolePath)) app.log.Trace(fmt.Sprintf("try create '%s' dir", name)) if err := os.Mkdir(rolePath, 0755); os.IsExist(err) { app.log.Warn(fmt.Sprintf("Папка \"%s\" уже существует", name)) return nil } for _, dir := range []string{"defaults", "files", "tasks", "templates", "vars"} { app.log.Trace(fmt.Sprintf("try create %s/%s dir", name, dir)) if err := utils.CreateDir(filepath.Join(rolePath, dir)); err != nil { return err } } for _, dir := range []string{"defaults", "vars"} { app.log.Trace(fmt.Sprintf("try create %s/%s/main.yml", name, dir)) if err := utils.WriteEmptyYaml(filepath.Join(rolePath, dir, "main.yml")); err != nil { return err } } { // tasks dir app.log.Trace("generate main task") if err := generateMainTask(name, rolePath); err != nil { return err } app.log.Trace("create install task") if err := utils.WriteEmptyYaml(filepath.Join(rolePath, "tasks", "install.yml")); err != nil { return err } } app.log.Trace("generate readme") if err := generateReadme(name, rolePath); err != nil { return err } app.log.Trace("read playbook.yml") playbook, err := types.ReadPlaybook(app.workDir) if err != nil { return err } app.log.Trace("append role to playbook.yml") playbook.Roles = append(playbook.Roles, name) app.log.Trace("write playbook.yml") if err := types.WritePlaybook(app.workDir, playbook); err != nil { return err } app.log.Info(fmt.Sprintf("Роль %s создана", name)) return nil } func generateMainTask(name string, path string) error { buffer := &bytes.Buffer{} buffer.WriteString(utils.YamlHeader()) buffer.WriteString(`- include_tasks: install.yml when: > (dd_install is defined and dd_install) `) buffer.WriteString(fmt.Sprintf(" or (dd_install_%s is defined and dd_install_%s)\n", name, name)) return os.WriteFile(filepath.Join(path, "tasks", "main.yml"), buffer.Bytes(), 0644) } func generateReadme(name string, path string) error { buffer := &bytes.Buffer{} buffer.WriteString(fmt.Sprintf("# %s\n\n", name)) buffer.Write(staticReadme) return os.WriteFile(filepath.Join(path, "README.MD"), buffer.Bytes(), 0644) } //--------------------------------------------------------------------------------------------------------------------// func (app SpaceWorker) RemoveRole(name string) error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Debug(fmt.Sprintf("role name: %s", name)) rolePath := filepath.Join(app.workDir, "roles", name) app.log.Debug(fmt.Sprintf("rolePath: %s", rolePath)) app.log.Trace(fmt.Sprintf("try remove '%s' dir", name)) if err := os.RemoveAll(rolePath); err != nil { return err } app.log.Trace("read playbook.yml") playbook, err := types.ReadPlaybook(app.workDir) if err != nil { return err } app.log.Trace("remove role from playbook.yml") idx := slices.Index(playbook.Roles, name) if idx >= 0 { playbook.Roles = slices.Delete(playbook.Roles, idx, idx+1) } app.log.Trace("write playbook.yml") if err := types.WritePlaybook(app.workDir, playbook); err != nil { return err } app.log.Info(fmt.Sprintf("Роль %s удалена", name)) return nil } func (app *SpaceWorker) ListRoles() error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Trace("try read playbook.yml") playbook, err := types.ReadPlaybook(app.workDir) if err != nil { return err } fmt.Println("Доступные роли:") for _, roleName := range playbook.Roles { fmt.Printf(" - %s\n", roleName) } return nil } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func (app *SpaceWorker) Install(generateOnly bool, roles ...string) error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) ansibleArgs := setupAnsibleArgs(app.AnsibleVerbose, roles) command := exec.Command(app.AnsibleBin, ansibleArgs...) command.Dir = app.workDir command.Stdin = os.Stdin command.Stdout = os.Stdout command.Stderr = os.Stderr if generateOnly { fmt.Println(strings.Join(command.Args, " ")) } else { app.log.Debug(fmt.Sprintf("ansible command line: %s", command.Args)) _ = command.Run() } return nil } func setupAnsibleArgs(verbose uint8, roles []string) []string { var ansibleArgs []string switch verbose { case 1: ansibleArgs = append(ansibleArgs, "-v") case 2: ansibleArgs = append(ansibleArgs, "-vv") case 3: ansibleArgs = append(ansibleArgs, "-vvv") } ansibleArgs = append(ansibleArgs, "-i", "hosts.yml") if len(roles) > 0 { for _, role := range roles { ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_install_%s=true", role)) } } else { ansibleArgs = append(ansibleArgs, "--extra-vars", "dd_install=true") } return append(ansibleArgs, "playbook.yml") } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// func (app *SpaceWorker) ListHosts() error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Trace("try read hosts.yml") hosts, err := types.ReadHosts(app.workDir) if err != nil { return err } fmt.Println("Хосты:") for serverName, props := range *hosts { fmt.Printf(" - %s [%s:%d] (%s)\n", serverName, props.Host, props.Port, props.User) } return nil } func (app *SpaceWorker) HostsAdd(name string, host string, port uint16, user string) error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Trace("try read hosts.yml") hosts, err := types.ReadHosts(app.workDir) if err != nil { return err } app.log.Trace("append new host to hosts.yml") (*hosts)[name] = types.THostProps{ Host: host, Port: port, User: user, Interpreter: types.DefaultInterpreter, } app.log.Trace("write hosts.yml") if err := types.WriteHosts(app.workDir, hosts); err != nil { return err } app.log.Trace(fmt.Sprintf("create vars/%s.vars.yml", name)) err = utils.WriteEmptyYaml(filepath.Join(app.workDir, "vars", fmt.Sprintf("%s.vars.yml", name))) if err != nil { return err } app.log.Info("Новый хост добавлен") return nil } func (app *SpaceWorker) HostRemove(name string) error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Trace("try read hosts.yml") hosts, err := types.ReadHosts(app.workDir) if err != nil { return err } app.log.Trace("remove host from hosts.yml") delete(*hosts, name) app.log.Trace("write hosts.yml") if err := types.WriteHosts(app.workDir, hosts); err != nil { return err } app.log.Trace(fmt.Sprintf("remove vars/%s.vars.yml", name)) err = utils.RemoveFile(filepath.Join(app.workDir, "vars", fmt.Sprintf("%s.vars.yml", name))) if err != nil { return err } app.log.Info("Хост удалён") return nil } func (app *SpaceWorker) SetDefaultHost(name string) error { app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir)) app.log.Trace("try read playbook.yml") playbook, err := types.ReadPlaybook(app.workDir) if err != nil { return err } app.log.Trace("set default host") playbook.Hosts = name app.log.Trace("write playbook.yml") if err := types.WritePlaybook(app.workDir, playbook); err != nil { return err } app.log.Info(fmt.Sprintf("Хост '%s' установлен по-умолчанию", name)) return nil }