Archived
1

first commit

This commit is contained in:
2024-12-23 01:55:48 +03:00
commit b514cc36fb
33 changed files with 1969 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
package space_creator
import (
_ "embed"
"fmt"
"os"
"path/filepath"
"playbookctl/internal/types"
"playbookctl/internal/utils"
"playbookctl/internal/utils/logger"
)
//goland:noinspection SpellCheckingInspection
var (
//go:embed static/gitignore.txt
staticGitIgnore []byte
//go:embed static/editorconfig.txt
staticEditorConfig []byte
//go:embed static/dd.datetime.sh
staticLibDatetime []byte
//go:embed static/dd.restore.sh
staticLibRestore []byte
//go:embed static/dd.sethostname.sh
staticLibSetHostname []byte
)
type ServerProps struct {
Name string
Host string
Port uint16
User string
}
type SpaceCreator struct {
log *logger.Logger
// Рабочая папка
workDir string
}
func NewSpaceCreator(verbose logger.LogVerbose, workDir string) *SpaceCreator {
return &SpaceCreator{
log: &logger.Logger{Verbose: verbose},
workDir: workDir,
}
}
// CreateSpace Создать пространство
func (app *SpaceCreator) CreateSpace(name string, props *ServerProps) error {
app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir))
app.log.Debug(fmt.Sprintf("space name: %s", name))
app.log.Debug(fmt.Sprintf("server props: %+v", props))
spacePath := filepath.Join(app.workDir, name)
app.log.Trace(fmt.Sprintf("try create folder '%s'", name))
if err := os.Mkdir(spacePath, 0755); os.IsExist(err) {
return fmt.Errorf("Папка \"%s\" уже существует\n", name)
}
for _, dir := range []string{"roles", "library", "vars"} {
app.log.Trace(fmt.Sprintf("try create folder '%s/%s'", name, dir))
if err := os.Mkdir(filepath.Join(spacePath, dir), 0755); err != nil {
return err
}
}
type StaticPair struct {
Name string
Data []byte
}
{ // root dir
app.log.Trace("generate hosts.yaml")
if err := generateHosts(spacePath, props); err != nil {
return err
}
app.log.Trace("generate playbook.yml")
if err := generatePlaybook(spacePath); err != nil {
return err
}
app.log.Trace("generate default.host.txt")
if err := utils.WriteStringFile(filepath.Join(spacePath, "default.host.txt"), props.Name); err != nil {
return err
}
for _, pair := range []StaticPair{
{".gitignore", staticGitIgnore},
{".editorconfig", staticEditorConfig},
} {
app.log.Trace(fmt.Sprintf("export %s", pair.Name))
if err := utils.SaveStaticFile(filepath.Join(spacePath, pair.Name), pair.Data); err != nil {
return err
}
}
}
{ // library dir
for _, pair := range []StaticPair{
{"dd.datetime.sh", staticLibDatetime},
{"dd.restore.sh", staticLibRestore},
{"dd.sethostname.sh", staticLibSetHostname},
} {
app.log.Trace(fmt.Sprintf("export library/%s", pair.Name))
if err := utils.SaveStaticFile(filepath.Join(spacePath, "library", pair.Name), pair.Data); err != nil {
return err
}
}
}
{ // vars dir
app.log.Trace(fmt.Sprintf("create vars/%s.vars.yml", props.Name))
err := utils.WriteEmptyYaml(filepath.Join(spacePath, "vars", fmt.Sprintf("%s.vars.yml", props.Name)))
if err != nil {
return err
}
}
app.log.Info(fmt.Sprintf("Пространство \"%s\" создано.", name))
return nil
}
func generateHosts(spacePath string, props *ServerProps) error {
hosts := types.THosts{
props.Name: types.THostProps{
Host: props.Host,
Port: props.Port,
User: props.User,
Interpreter: types.DefaultInterpreter,
},
}
return types.WriteHosts(spacePath, &hosts)
}
func generatePlaybook(spacePath string) error {
playbook := types.Playbook{
Hosts: "all",
GatherFacts: true,
PreTasks: []types.Task{{
Name: "Include vars",
IncludeVars: "vars/{{ inventory_hostname }}.vars.yml",
}},
Roles: []string{},
}
return types.WritePlaybook(spacePath, &playbook)
}

View File

@@ -0,0 +1,20 @@
#!/bin/bash
# vi: set tabstop=4 shiftwidth=4 noexpandtab :
#------------------------------------------------#
# Модуль получения текущего даты-времени.
#
# . . . . . . . . . . . . . . . . . . . . . . . .
# - dd.datetime:
# register: current_datetime
#------------------------------------------------#
source $1
set -euo pipefail
L_CHANGED=false
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
__curr_date=$(date +%Y%m%d_%H%M%S)
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
echo "{ \"changed\": ${L_CHANGED}, \"datetime\": \"$__curr_date\" }"

View File

@@ -0,0 +1,28 @@
#!/bin/bash
# vi: set tabstop=4 shiftwidth=4 noexpandtab :
#------------------------------------------------#
# Модуль получения даты последнего бэкапа, если
# в качесте даты указано "latest". Иначе,
# возвращает переданный параметр.
#
# . . . . . . . . . . . . . . . . . . . . . . . .
# - dd.restore:
# role_path: "{{ role_path }}"
# inventory: name_of_inventory
# datetime: 20240412_231753
# register: res_dd_restore
#------------------------------------------------#
source $1
set -euo pipefail
L_CHANGED=false
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
if [[ "$datetime" == "latest" ]]; then
cd "$role_path/backups/$inventory"
datetime=$(ls -1t | head -1)
fi
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
echo "{ \"changed\": $L_CHANGED, \"inventory\": \"$inventory\", \"datetime\": \"$datetime\" }"

View File

@@ -0,0 +1,63 @@
#!/bin/bash
# vi: set tabstop=4 shiftwidth=4 noexpandtab :
#------------------------------------------------#
# Модуль настройки hostname.
#
# В отличии от стокового модуля "hostname",
# данный модуль так же патчит файл `/etc/hosts`.
# . . . . . . . . . . . . . . . . . . . . . . . .
# - dd.sethostname:
# name: my-virtual-machine
#------------------------------------------------#
source $1
set -euo pipefail
__curr_hostname="$(hostname)"
__changed=false
if [[ "$__curr_hostname" != "$name" ]]; then
hostnamectl set-hostname "$name"
perl - "$__curr_hostname" "$name" <<'EOP'
#!perl
use strict;
use warnings;
use Data::Dumper qw(Dumper);
my ($oldhost, $newhost) = @ARGV;
my %hosts;
open(FILE_HOSTS, "<", "/etc/hosts") || die $!;
while(<FILE_HOSTS>) {
chomp;
if (/^#/ || /^\s*$/) {
next;
}
my @pair = split(/\s+/, $_, 2);
my @values = split(/\s+/, $pair[1]);
$hosts{$pair[0]} = [@values];
}
close(FILE_HOSTS);
while (my ($key, $value) = each(%hosts)) {
my $i = 0;
foreach my $host (@{ $value }) {
if ($host eq $oldhost) {
@{ $value }[$i] = $newhost;
$hosts{$key} = [@{ $value }[$i]];
}
$i = $i + 1;
}
}
open(FILE_HOSTS, ">", "/etc/hosts") || die $!;
foreach my $key (sort(keys(%hosts))) {
print FILE_HOSTS "$key @{$hosts{$key}}\n";
}
close(FILE_HOSTS);
EOP
__changed=true
fi
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##
echo "{ \"changed\": $__changed }"

View File

@@ -0,0 +1,15 @@
root = true
[*.{sh,yml}]
charset = utf-8
end_of_line = lf
indent_style = space
insert_final_newline = true
[*.sh]
indent_size = 4
max_line_length = 80
[*.yml]
indent_size = 2
max_line_length = 80

View File

@@ -0,0 +1,5 @@
# Jet Brains IDEA
/.idea/
# Other
*.bak

View File

@@ -0,0 +1,103 @@
package space_worker
import (
"fmt"
"os"
"os/exec"
"playbookctl/internal/utils"
"strings"
"time"
)
func (app *SpaceWorker) Backup(generateOnly bool, targetHost string, roles ...string) error {
app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir))
var host string
if targetHost != "" {
host = targetHost
} else {
var err error
host, err = app.GetDefaultHost()
if err != nil {
return err
}
}
postBackupFile := fmt.Sprintf("/tmp/%s", time.Now().Format("20060102_150405"))
ansibleArgs, err := app.setupBackupAnsibleArgs(app.AnsibleVerbose, host, roles, postBackupFile)
if err != nil {
return err
}
command := exec.Command(app.AnsibleBin, ansibleArgs...)
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
app.log.Info(fmt.Sprintf("Target Host: %s", host))
if generateOnly {
fmt.Println(strings.Join(command.Args, " "))
return nil
}
app.log.Debug(fmt.Sprintf("ansible command line: %s", command.Args))
_ = command.Run()
app.log.Info("Download Files")
command = exec.Command("/bin/bash", "-c", postBackupFile)
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
_ = command.Run()
if err = utils.RemoveFile(postBackupFile); err != nil {
return err
}
command = exec.Command("/bin/bash", "-c", postBackupFile+"_clean")
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
_ = command.Run()
if err = utils.RemoveFile(postBackupFile + "_clean"); err != nil {
return err
}
return nil
}
func (app *SpaceWorker) setupBackupAnsibleArgs(verbose uint8, targetHost string, roles []string, postBackupFile string) ([]string, error) {
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")
ansibleArgs = append(ansibleArgs, "-l", targetHost)
if len(roles) > 0 {
for _, role := range roles {
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_backup_%s=true", role))
}
} else {
ansibleArgs = append(ansibleArgs, "--extra-vars", "dd_backup=true")
}
ansibleArgs = append(ansibleArgs, "--extra-vars", "dd_postbackup=true")
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_postbackup_file=%s", postBackupFile))
return append(ansibleArgs, "playbook.yml"), nil
}

View File

@@ -0,0 +1,119 @@
package space_worker
import (
"fmt"
"path/filepath"
"playbookctl/internal/types"
"playbookctl/internal/utils"
)
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
}
app.log.Trace("try read default.host.txt")
defaultHost, err := utils.ReadStringFile(filepath.Join(app.workDir, "default.host.txt"))
if err != nil {
return err
}
fmt.Println("Хосты:")
for serverName, props := range *hosts {
defaultMarker := ""
if serverName == defaultHost {
defaultMarker = " *"
}
fmt.Printf(" - %s [%s:%d] (%s)%s\n", serverName, props.Host, props.Port, props.User, defaultMarker)
}
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 write default.host.txt")
err := utils.WriteStringFile(filepath.Join(app.workDir, "default.host.txt"), name)
if err != nil {
return err
}
app.log.Info(fmt.Sprintf("Хост '%s' установлен по-умолчанию", name))
return nil
}
func (app *SpaceWorker) GetDefaultHost() (string, error) {
app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir))
app.log.Trace("try read default.host.txt")
value, err := utils.ReadStringFile(filepath.Join(app.workDir, "default.host.txt"))
if err != nil {
return "", err
}
return value, nil
}

View File

@@ -0,0 +1,71 @@
package space_worker
import (
"fmt"
"os"
"os/exec"
"strings"
)
func (app *SpaceWorker) Install(generateOnly bool, targetHost string, roles ...string) error {
app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir))
var host string
if targetHost != "" {
host = targetHost
} else {
var err error
host, err = app.GetDefaultHost()
if err != nil {
return err
}
}
ansibleArgs, err := app.setupInstallAnsibleArgs(app.AnsibleVerbose, host, roles)
if err != nil {
return err
}
command := exec.Command(app.AnsibleBin, ansibleArgs...)
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
app.log.Info(fmt.Sprintf("Target Host: %s", host))
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 (app *SpaceWorker) setupInstallAnsibleArgs(verbose uint8, targetHost string, roles []string) ([]string, error) {
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")
ansibleArgs = append(ansibleArgs, "-l", targetHost)
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"), nil
}

View File

@@ -0,0 +1,139 @@
package space_worker
import (
"fmt"
"os"
"os/exec"
"playbookctl/internal/utils"
"time"
)
func (app *SpaceWorker) Restore(targetHost string, timestamp string, inventory string, roles ...string) error {
app.log.Debug(fmt.Sprintf("workDir: %s", app.workDir))
var host string
if targetHost != "" {
host = targetHost
} else {
var err error
host, err = app.GetDefaultHost()
if err != nil {
return err
}
}
preRestoreFile := fmt.Sprintf("/tmp/%s", time.Now().Format("20060102_150405"))
ansibleArgs, err := app.setupPreRestoreAnsibleArgs(app.AnsibleVerbose, host, roles, preRestoreFile, timestamp, inventory)
if err != nil {
return err
}
command := exec.Command(app.AnsibleBin, ansibleArgs...)
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
app.log.Info(fmt.Sprintf("Target Host: %s", host))
app.log.Info("Pre restore Ansible")
_ = command.Run()
app.log.Info("Send files")
command = exec.Command("/bin/bash", "-c", preRestoreFile)
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
_ = command.Run()
app.log.Info("Restore Ansible")
ansibleArgs, err = app.setupRestoreAnsibleArgs(app.AnsibleVerbose, host, roles, preRestoreFile, timestamp, inventory)
if err != nil {
return err
}
command = exec.Command(app.AnsibleBin, ansibleArgs...)
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
_ = command.Run()
app.log.Info("Clean temp files")
command = exec.Command("/bin/bash", "-c", preRestoreFile+"_clean")
command.Dir = app.workDir
command.Stdin = os.Stdin
command.Stdout = os.Stdout
command.Stderr = os.Stderr
_ = command.Run()
if err = utils.RemoveFile(preRestoreFile); err != nil {
return err
}
if err = utils.RemoveFile(preRestoreFile + "_clean"); err != nil {
return err
}
return nil
}
func (app *SpaceWorker) setupPreRestoreAnsibleArgs(verbose uint8, targetHost string, roles []string, preRestoreFile string, timestamp string, inventory string) ([]string, error) {
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")
ansibleArgs = append(ansibleArgs, "-l", targetHost)
if len(roles) > 0 {
for _, role := range roles {
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_prerestore_%s=true", role))
}
} else {
ansibleArgs = append(ansibleArgs, "--extra-vars", "dd_prerestore=true")
}
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_restore_datetime=%s", timestamp))
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_restore_inventory=%s", inventory))
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_prerestore_file=%s", preRestoreFile))
return append(ansibleArgs, "playbook.yml"), nil
}
func (app *SpaceWorker) setupRestoreAnsibleArgs(verbose uint8, targetHost string, roles []string, preRestoreFile string, timestamp string, inventory string) ([]string, error) {
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")
ansibleArgs = append(ansibleArgs, "-l", targetHost)
if len(roles) > 0 {
for _, role := range roles {
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_restore_%s=true", role))
}
} else {
ansibleArgs = append(ansibleArgs, "--extra-vars", "dd_restore=true")
}
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_restore_datetime=%s", timestamp))
ansibleArgs = append(ansibleArgs, "--extra-vars", fmt.Sprintf("dd_restore_inventory=%s", inventory))
return append(ansibleArgs, "playbook.yml"), nil
}

View File

@@ -0,0 +1,202 @@
package space_worker
import (
"bytes"
"fmt"
"os"
"path/filepath"
"playbookctl/internal/types"
"playbookctl/internal/utils"
"slices"
)
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, false); 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, withBackup bool) error {
var mainTask []types.MainTask
if withBackup {
mainTask = make([]types.MainTask, 4)
} else {
mainTask = make([]types.MainTask, 1)
}
mainTask[0] = types.MainTask{
IncludeTasks: "install.yml",
When: fmt.Sprintf("(dd_install is defined and dd_install) "+
"or (dd_install_%s is defined and dd_install_%s)", name, name),
}
if withBackup {
mainTask[1] = types.MainTask{
IncludeTasks: "backup.yml",
When: fmt.Sprintf("(dd_backup is defined and dd_backup) "+
"or (dd_backup_%s is defined and dd_backup_%s)", name, name),
}
mainTask[2] = types.MainTask{
IncludeTasks: "pre-restore.yml",
When: fmt.Sprintf("(dd_prerestore is defined and dd_prerestore) "+
"or (dd_prerestore_%s is defined and dd_prerestore_%s)", name, name),
}
mainTask[3] = types.MainTask{
IncludeTasks: "restore.yml",
When: fmt.Sprintf("(dd_restore is defined and dd_restore) "+
"or (dd_restore_%s is defined and dd_restore_%s)", name, name),
}
}
return types.WriteMainTask(path, &mainTask)
}
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) ModifyRoleBackupAdd(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))
for _, file := range []string{"backup.yml", "pre-restore.yml", "restore.yml"} {
app.log.Trace(fmt.Sprintf("try create %s", file))
if err := utils.WriteEmptyYaml(filepath.Join(rolePath, "tasks", file)); err != nil {
return err
}
}
app.log.Trace("re-generate main task")
if err := generateMainTask(name, rolePath, true); err != nil {
return err
}
app.log.Trace("try create backups dir")
if err := utils.CreateDirIfNotExists(filepath.Join(rolePath, "backups")); err != nil {
return err
}
app.log.Trace("create .gitignore")
if err := utils.WriteStringFile(filepath.Join(rolePath, ".gitignore"), "backups/"); err != nil {
return err
}
return nil
}

View File

@@ -0,0 +1,28 @@
package space_worker
import (
_ "embed"
"playbookctl/internal/utils/logger"
)
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,
}
}

View File

@@ -0,0 +1,4 @@
## Переменные
| Name | Description | Default |
|------|-------------|---------|

48
internal/types/hosts.go Normal file
View File

@@ -0,0 +1,48 @@
package types
import (
"gopkg.in/yaml.v3"
"os"
"path/filepath"
"playbookctl/internal/utils"
)
var DefaultInterpreter = "/usr/bin/python3"
type THostProps struct {
Host string `yaml:"ansible_host"`
Port uint16 `yaml:"ansible_port"`
User string `yaml:"ansible_user"`
Interpreter string `yaml:"ansible_python_interpreter"`
}
type THosts map[string]THostProps
type tUngrouped struct {
Hosts THosts `yaml:"hosts"`
}
type tHostsConfig struct {
Ungrouped tUngrouped `yaml:"ungrouped"`
}
func ReadHosts(workDir string) (*THosts, error) {
var hostsConf tHostsConfig
{
bb, err := os.ReadFile(filepath.Join(workDir, "hosts.yml"))
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(bb, &hostsConf); err != nil {
return nil, err
}
}
return &hostsConf.Ungrouped.Hosts, nil
}
func WriteHosts(workDir string, hosts *THosts) error {
hostsConf := tHostsConfig{Ungrouped: tUngrouped{Hosts: *hosts}}
return utils.WriteYaml(&hostsConf, filepath.Join(workDir, "hosts.yml"))
}

View File

@@ -0,0 +1,15 @@
package types
import (
"path/filepath"
"playbookctl/internal/utils"
)
type MainTask struct {
IncludeTasks string `yaml:"include_tasks"`
When string `yaml:"when"`
}
func WriteMainTask(roleDir string, mainTask *[]MainTask) error {
return utils.WriteYaml(mainTask, filepath.Join(roleDir, "tasks", "main.yml"))
}

View File

@@ -0,0 +1,40 @@
package types
import (
"gopkg.in/yaml.v3"
"os"
"path/filepath"
"playbookctl/internal/utils"
)
type Task struct {
Name string `yaml:"name"`
IncludeVars string `yaml:"include_vars"`
}
type Playbook struct {
Hosts string `yaml:"hosts"`
GatherFacts bool `yaml:"gather_facts"`
PreTasks []Task `yaml:"pre_tasks"`
Roles []string `yaml:"roles"`
}
func ReadPlaybook(workDir string) (*Playbook, error) {
var playbook []Playbook
{
bb, err := os.ReadFile(filepath.Join(workDir, "playbook.yml"))
if err != nil {
return nil, err
}
if err := yaml.Unmarshal(bb, &playbook); err != nil {
return nil, err
}
}
return &playbook[0], nil
}
func WritePlaybook(workDir string, playbook *Playbook) error {
return utils.WriteYaml([]*Playbook{playbook}, filepath.Join(workDir, "playbook.yml"))
}

View File

@@ -0,0 +1,60 @@
package utils
import (
"bytes"
"os"
)
func SaveStaticFile(path string, data []byte) error {
return os.WriteFile(path, data, 0644)
}
func CreateDir(path string) error {
return os.Mkdir(path, 0755)
}
func CreateDirIfNotExists(path string) error {
exists, err := IsExistsDir(path)
if err != nil {
return err
}
if !exists {
if err = CreateDir(path); err != nil {
return err
}
}
return nil
}
func IsExistsDir(path string) (bool, error) {
_, err := os.Stat(path)
if err == nil {
return true, nil
}
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
func RemoveFile(path string) error {
return os.Remove(path)
}
func WriteStringFile(path string, data string) error {
buffer := &bytes.Buffer{}
buffer.WriteString(data)
return os.WriteFile(path, buffer.Bytes(), 0644)
}
func ReadStringFile(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(data), nil
}

View File

@@ -0,0 +1,39 @@
package logger
import "fmt"
type LogVerbose uint8
const (
Warning LogVerbose = 0
Info = 1
Debug = 2
Trace = 3
)
type Logger struct {
// Уровень логирования
Verbose LogVerbose
}
func (log *Logger) Warn(message string) {
fmt.Printf("[warning] %s\n", message)
}
func (log *Logger) Info(message string) {
if log.Verbose >= Info {
fmt.Printf("[info] %s\n", message)
}
}
func (log *Logger) Debug(message string) {
if log.Verbose >= Debug {
fmt.Printf("[debug] %s\n", message)
}
}
func (log *Logger) Trace(message string) {
if log.Verbose >= Trace {
fmt.Printf("[trace] %s\n", message)
}
}

View File

@@ -0,0 +1,36 @@
package utils
import (
"bytes"
"gopkg.in/yaml.v3"
"os"
)
func YamlHeader() string {
return "# vi: set tabstop=2 shiftwidth=2 expandtab :\n---\n"
}
func WriteYaml(data interface{}, yamlFile string) error {
buffer := &bytes.Buffer{}
buffer.WriteString(YamlHeader())
yamlEncoder := yaml.NewEncoder(buffer)
yamlEncoder.SetIndent(2)
if err := yamlEncoder.Encode(data); err != nil {
return err
}
if err := os.WriteFile(yamlFile, buffer.Bytes(), 0644); err != nil {
return err
}
return nil
}
func WriteEmptyYaml(yamlFile string) error {
buffer := &bytes.Buffer{}
buffer.WriteString(YamlHeader())
return os.WriteFile(yamlFile, buffer.Bytes(), 0644)
}