first commit
This commit is contained in:
15
.editorconfig
Normal file
15
.editorconfig
Normal 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
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
.idea/
|
||||||
|
target/
|
||||||
12
Makefile
Normal file
12
Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!make
|
||||||
|
|
||||||
|
# ldflags:
|
||||||
|
# -s: Убирает информацию о символах (например, дебаг-символы).
|
||||||
|
# -w: Убирает информацию о отладке, включая таблицу строк и информацию о файлах.
|
||||||
|
|
||||||
|
build:
|
||||||
|
GOOS=linux go build -ldflags="-s -w" -buildmode=exe -o target/playbookctl main.go \
|
||||||
|
&& strip target/playbookctl
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf target/*
|
||||||
69
cmd/backup.go
Normal file
69
cmd/backup.go
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"playbookctl/internal/space_worker"
|
||||||
|
"playbookctl/internal/types"
|
||||||
|
"playbookctl/internal/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommandBackup() *cobra.Command {
|
||||||
|
backupCmd := &cobra.Command{
|
||||||
|
Use: "backup [role1, role2, ...]",
|
||||||
|
Short: "выполнить резервное копирование пространства",
|
||||||
|
Args: backupCheckArgsE,
|
||||||
|
ValidArgsFunction: ArgRoleCompletion,
|
||||||
|
RunE: backupRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
backupCmd.Flags().Uint8Var(&flagAnsibleVerbose, "ansible-verbose", 0, "ansible verbose mode (0-3)")
|
||||||
|
backupCmd.Flags().StringVar(&flagAnsibleBin, "ansible-bin", "/usr/bin/ansible-playbook", "путь к ansible-playbook")
|
||||||
|
backupCmd.Flags().BoolVar(&flagGenOnly, "generate-only", false, "только сгенерировать исполняемую команду")
|
||||||
|
backupCmd.Flags().StringVar(&flagTargetHost, "target", "", "имя целевого хоста")
|
||||||
|
|
||||||
|
if err := backupCmd.MarkFlagFilename("ansible-bin"); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return backupCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupCheckArgsE(_ *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
playbook, err := types.ReadPlaybook(workDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roleName := range args {
|
||||||
|
if !containsInSlice(roleName, playbook.Roles) {
|
||||||
|
return fmt.Errorf("роли \"%s\" не существует или она не добавлена в playbook.yml", roleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func backupRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
spaceWorker.AnsibleBin = flagAnsibleBin
|
||||||
|
spaceWorker.AnsibleVerbose = flagAnsibleVerbose
|
||||||
|
return spaceWorker.Backup(flagGenOnly, flagTargetHost, args...)
|
||||||
|
}
|
||||||
181
cmd/hosts.go
Normal file
181
cmd/hosts.go
Normal file
@@ -0,0 +1,181 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"playbookctl/internal/space_worker"
|
||||||
|
"playbookctl/internal/types"
|
||||||
|
"playbookctl/internal/utils/logger"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommandHosts() *cobra.Command {
|
||||||
|
hostsCmd := &cobra.Command{
|
||||||
|
Use: "hosts",
|
||||||
|
Short: "работа с хостами",
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
hostsCmd.AddCommand(newCommandHostsList())
|
||||||
|
hostsCmd.AddCommand(newCommandHostsAdd())
|
||||||
|
hostsCmd.AddCommand(newCommandHostsRemove())
|
||||||
|
hostsCmd.AddCommand(newCommandHostsDefault())
|
||||||
|
|
||||||
|
return hostsCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandHostsList() *cobra.Command {
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "список хостов",
|
||||||
|
RunE: hostsListRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return listCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostsListRunE(_ *cobra.Command, _ []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.ListHosts()
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandHostsAdd() *cobra.Command {
|
||||||
|
addCmd := &cobra.Command{
|
||||||
|
Use: "add <name> <host[:port]> [user]",
|
||||||
|
Aliases: []string{"new"},
|
||||||
|
Short: "добавить новый хост",
|
||||||
|
Args: cobra.MinimumNArgs(2),
|
||||||
|
RunE: hostsAddRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return addCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostsAddRunE(_ *cobra.Command, args []string) error {
|
||||||
|
var (
|
||||||
|
name string
|
||||||
|
host string
|
||||||
|
port uint16
|
||||||
|
user string
|
||||||
|
)
|
||||||
|
|
||||||
|
name = args[0]
|
||||||
|
|
||||||
|
if idx := strings.Index(args[1], ":"); idx > 0 {
|
||||||
|
split := strings.Split(args[1], ":")
|
||||||
|
host = split[0]
|
||||||
|
|
||||||
|
parsedInt, err := strconv.ParseInt(split[1], 0, 16)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
port = uint16(parsedInt)
|
||||||
|
} else {
|
||||||
|
host = args[1]
|
||||||
|
port = 22
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(args) == 2 {
|
||||||
|
user = "root"
|
||||||
|
} else if len(args) == 3 {
|
||||||
|
user = args[2]
|
||||||
|
}
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.HostsAdd(name, host, port, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandHostsRemove() *cobra.Command {
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "remove <name>",
|
||||||
|
Aliases: []string{"delete", "del", "rm"},
|
||||||
|
Short: "удалить хост",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: hostsRemoveValidateArgs,
|
||||||
|
RunE: hostsRemoveRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return listCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostsRemoveValidateArgs(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
exitApp := func(err error) {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestions []string
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
exitApp(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := types.ReadHosts(workDir)
|
||||||
|
if err != nil {
|
||||||
|
exitApp(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for host := range *hosts {
|
||||||
|
if toComplete == "" || len(host) >= len(toComplete) && host[:len(toComplete)] == toComplete {
|
||||||
|
suggestions = append(suggestions, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostsRemoveRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.HostRemove(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandHostsDefault() *cobra.Command {
|
||||||
|
command := &cobra.Command{
|
||||||
|
Use: "set-default",
|
||||||
|
Short: "установка хоста по-умолчанию",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: ArgHostCompletion,
|
||||||
|
RunE: hostsDefaultRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return command
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostsDefaultRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.SetDefaultHost(args[0])
|
||||||
|
}
|
||||||
79
cmd/install.go
Normal file
79
cmd/install.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"playbookctl/internal/space_worker"
|
||||||
|
"playbookctl/internal/types"
|
||||||
|
"playbookctl/internal/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommandInstall() *cobra.Command {
|
||||||
|
installCmd := &cobra.Command{
|
||||||
|
Use: "install [role1, role2, ...]",
|
||||||
|
Short: "применить настройки пространства к серверу",
|
||||||
|
Args: installCheckArgsE,
|
||||||
|
ValidArgsFunction: ArgRoleCompletion,
|
||||||
|
RunE: installRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
installCmd.Flags().Uint8Var(&flagAnsibleVerbose, "ansible-verbose", 0, "ansible verbose mode (0-3)")
|
||||||
|
installCmd.Flags().StringVar(&flagAnsibleBin, "ansible-bin", "/usr/bin/ansible-playbook", "путь к ansible-playbook")
|
||||||
|
installCmd.Flags().BoolVar(&flagGenOnly, "generate-only", false, "только сгенерировать исполняемую команду")
|
||||||
|
installCmd.Flags().StringVar(&flagTargetHost, "target", "", "имя целевого хоста")
|
||||||
|
|
||||||
|
if err := installCmd.MarkFlagFilename("ansible-bin"); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return installCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func installCheckArgsE(_ *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
playbook, err := types.ReadPlaybook(workDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roleName := range args {
|
||||||
|
if !containsInSlice(roleName, playbook.Roles) {
|
||||||
|
return fmt.Errorf("роли \"%s\" не существует или она не добавлена в playbook.yml", roleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func installRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
spaceWorker.AnsibleBin = flagAnsibleBin
|
||||||
|
spaceWorker.AnsibleVerbose = flagAnsibleVerbose
|
||||||
|
return spaceWorker.Install(flagGenOnly, flagTargetHost, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func containsInSlice(element string, slice []string) bool {
|
||||||
|
for _, item := range slice {
|
||||||
|
if item == element {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
76
cmd/restore.go
Normal file
76
cmd/restore.go
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"playbookctl/internal/space_worker"
|
||||||
|
"playbookctl/internal/types"
|
||||||
|
"playbookctl/internal/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagRestoreTimestamp string
|
||||||
|
flagRestoreInventory string
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommandRestore() *cobra.Command {
|
||||||
|
restoreCmd := &cobra.Command{
|
||||||
|
Use: "restore [role1, role2, ...]",
|
||||||
|
Short: "восстановить резервную копированию пространства",
|
||||||
|
Args: restoreCheckArgsE,
|
||||||
|
ValidArgsFunction: ArgRoleCompletion,
|
||||||
|
RunE: restoreRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
restoreCmd.Flags().Uint8Var(&flagAnsibleVerbose, "ansible-verbose", 0, "ansible verbose mode (0-3)")
|
||||||
|
restoreCmd.Flags().StringVar(&flagAnsibleBin, "ansible-bin", "/usr/bin/ansible-playbook", "путь к ansible-playbook")
|
||||||
|
restoreCmd.Flags().BoolVar(&flagGenOnly, "generate-only", false, "только сгенерировать исполняемую команду")
|
||||||
|
restoreCmd.Flags().StringVar(&flagTargetHost, "target", "", "имя целевого хоста")
|
||||||
|
restoreCmd.Flags().StringVar(&flagRestoreTimestamp, "timestamp", "latest", "выбор времени бекапа")
|
||||||
|
restoreCmd.Flags().StringVar(&flagRestoreInventory, "inventory", "", "выбор хост бекапа")
|
||||||
|
|
||||||
|
if err := restoreCmd.MarkFlagFilename("ansible-bin"); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return restoreCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreCheckArgsE(_ *cobra.Command, args []string) error {
|
||||||
|
if len(args) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
playbook, err := types.ReadPlaybook(workDir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, roleName := range args {
|
||||||
|
if !containsInSlice(roleName, playbook.Roles) {
|
||||||
|
return fmt.Errorf("роли \"%s\" не существует или она не добавлена в playbook.yml", roleName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := space_worker.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
spaceWorker.AnsibleBin = flagAnsibleBin
|
||||||
|
spaceWorker.AnsibleVerbose = flagAnsibleVerbose
|
||||||
|
return spaceWorker.Restore(flagTargetHost, flagRestoreTimestamp, flagRestoreInventory, args...)
|
||||||
|
}
|
||||||
130
cmd/role.go
Normal file
130
cmd/role.go
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"path/filepath"
|
||||||
|
sw "playbookctl/internal/space_worker"
|
||||||
|
"playbookctl/internal/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagBackupAdd bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommandRole() *cobra.Command {
|
||||||
|
roleCmd := &cobra.Command{
|
||||||
|
Use: "role",
|
||||||
|
Short: "работа с ролями в пространстве",
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
roleCmd.AddCommand(newCommandRoleCreate())
|
||||||
|
roleCmd.AddCommand(newCommandRoleRemove())
|
||||||
|
roleCmd.AddCommand(newCommandRoleList())
|
||||||
|
roleCmd.AddCommand(newCommandRoleModify())
|
||||||
|
|
||||||
|
return roleCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandRoleCreate() *cobra.Command {
|
||||||
|
createCmd := &cobra.Command{
|
||||||
|
Use: "create <name>",
|
||||||
|
Aliases: []string{"new"},
|
||||||
|
Short: "создать роль в пространстве",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
RunE: roleCreateRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return createCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleCreateRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := sw.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.CreateRole(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandRoleRemove() *cobra.Command {
|
||||||
|
createCmd := &cobra.Command{
|
||||||
|
Use: "remove <name>",
|
||||||
|
Aliases: []string{"delete", "rm", "del"},
|
||||||
|
Short: "удаляет роль из пространства",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: ArgRoleCompletion,
|
||||||
|
RunE: roleRemoveRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return createCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleRemoveRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := sw.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.RemoveRole(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandRoleList() *cobra.Command {
|
||||||
|
listCmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Aliases: []string{"ls"},
|
||||||
|
Short: "список ролей в пространстве",
|
||||||
|
RunE: roleListRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
return listCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleListRunE(_ *cobra.Command, _ []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
spaceWorker := sw.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceWorker.ListRoles()
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
func newCommandRoleModify() *cobra.Command {
|
||||||
|
modifyCmd := &cobra.Command{
|
||||||
|
Use: "modify <name>",
|
||||||
|
Short: "модифицировать роль",
|
||||||
|
Args: cobra.ExactArgs(1),
|
||||||
|
ValidArgsFunction: ArgRoleCompletion,
|
||||||
|
RunE: roleModifyRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyCmd.Flags().BoolVar(&flagBackupAdd, "backup-add", false, "добавить backup функцию")
|
||||||
|
|
||||||
|
return modifyCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func roleModifyRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceWorker := sw.NewSpaceWorker(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
if flagBackupAdd {
|
||||||
|
return spaceWorker.ModifyRoleBackupAdd(args[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
52
cmd/root.go
Normal file
52
cmd/root.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagVerbose uint8
|
||||||
|
flagWorkdir string
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagAnsibleVerbose uint8
|
||||||
|
flagAnsibleBin string
|
||||||
|
flagGenOnly bool
|
||||||
|
flagTargetHost string
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = &cobra.Command{
|
||||||
|
Use: "playbookctl",
|
||||||
|
Short: "Ansible Playbook Dedic2 Control",
|
||||||
|
Version: "4.0",
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.PersistentFlags().Uint8Var(&flagVerbose, "verbose", 1, "verbose mode (0-3)")
|
||||||
|
rootCmd.PersistentFlags().StringVar(&flagWorkdir, "workdir", ".", "рабочая директория")
|
||||||
|
|
||||||
|
if err := rootCmd.MarkPersistentFlagDirname("workdir"); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.AddCommand(NewCommandSpace())
|
||||||
|
rootCmd.AddCommand(NewCommandRole())
|
||||||
|
rootCmd.AddCommand(NewCommandInstall())
|
||||||
|
rootCmd.AddCommand(NewCommandHosts())
|
||||||
|
rootCmd.AddCommand(NewCommandBackup())
|
||||||
|
rootCmd.AddCommand(NewCommandRestore())
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
if err := rootCmd.Execute(); err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
70
cmd/space.go
Normal file
70
cmd/space.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
_ "embed"
|
||||||
|
"errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"path/filepath"
|
||||||
|
sc "playbookctl/internal/space_creator"
|
||||||
|
"playbookctl/internal/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
flagName string
|
||||||
|
flagHost string
|
||||||
|
flagPort uint16
|
||||||
|
flagUser string
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewCommandSpace() *cobra.Command {
|
||||||
|
spaceCmd := &cobra.Command{
|
||||||
|
Use: "space",
|
||||||
|
Short: "работа с пространствами",
|
||||||
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
||||||
|
return cmd.Help()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceCmd.AddCommand(newCommandSpaceCreate())
|
||||||
|
|
||||||
|
return spaceCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCommandSpaceCreate() *cobra.Command {
|
||||||
|
createCmd := &cobra.Command{
|
||||||
|
Use: "create <name>",
|
||||||
|
Aliases: []string{"new"},
|
||||||
|
Short: "создать пространство",
|
||||||
|
Args: spaceCreateCheckArgsE,
|
||||||
|
RunE: spaceCreateRunE,
|
||||||
|
}
|
||||||
|
|
||||||
|
createCmd.Flags().StringVar(&flagName, "name", "default", "псевдоним сервера")
|
||||||
|
createCmd.Flags().StringVar(&flagHost, "host", "127.0.0.1", "SSH адрес")
|
||||||
|
createCmd.Flags().Uint16Var(&flagPort, "port", 22, "SSH порт")
|
||||||
|
createCmd.Flags().StringVar(&flagUser, "user", "root", "SSH пользователь")
|
||||||
|
|
||||||
|
return createCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func spaceCreateCheckArgsE(_ *cobra.Command, args []string) error {
|
||||||
|
if len(args) != 1 {
|
||||||
|
return errors.New("должен быть указан один аргумент")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func spaceCreateRunE(_ *cobra.Command, args []string) error {
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
spaceCreator := sc.NewSpaceCreator(logger.LogVerbose(flagVerbose), workDir)
|
||||||
|
return spaceCreator.CreateSpace(args[0], &sc.ServerProps{
|
||||||
|
Name: flagName,
|
||||||
|
Host: flagHost,
|
||||||
|
Port: flagPort,
|
||||||
|
User: flagUser,
|
||||||
|
})
|
||||||
|
}
|
||||||
63
cmd/utils.go
Normal file
63
cmd/utils.go
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"playbookctl/internal/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ArgRoleCompletion(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
exitApp := func(err error) {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestions []string
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
exitApp(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
playbook, err := types.ReadPlaybook(workDir)
|
||||||
|
if err != nil {
|
||||||
|
exitApp(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, role := range playbook.Roles {
|
||||||
|
if toComplete == "" || len(role) >= len(toComplete) && role[:len(toComplete)] == toComplete {
|
||||||
|
suggestions = append(suggestions, role)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
|
|
||||||
|
func ArgHostCompletion(_ *cobra.Command, _ []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
exitApp := func(err error) {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var suggestions []string
|
||||||
|
|
||||||
|
workDir, err := filepath.Abs(flagWorkdir)
|
||||||
|
if err != nil {
|
||||||
|
exitApp(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts, err := types.ReadHosts(workDir)
|
||||||
|
if err != nil {
|
||||||
|
exitApp(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for host := range *hosts {
|
||||||
|
if toComplete == "" || len(host) >= len(toComplete) && host[:len(toComplete)] == toComplete {
|
||||||
|
suggestions = append(suggestions, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return suggestions, cobra.ShellCompDirectiveDefault
|
||||||
|
}
|
||||||
13
go.mod
Normal file
13
go.mod
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
module playbookctl
|
||||||
|
|
||||||
|
go 1.21
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/spf13/cobra v1.8.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
)
|
||||||
11
go.sum
Normal file
11
go.sum
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
|
||||||
|
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
154
internal/space_creator/space_creator.go
Normal file
154
internal/space_creator/space_creator.go
Normal 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)
|
||||||
|
}
|
||||||
20
internal/space_creator/static/dd.datetime.sh
Normal file
20
internal/space_creator/static/dd.datetime.sh
Normal 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\" }"
|
||||||
28
internal/space_creator/static/dd.restore.sh
Normal file
28
internal/space_creator/static/dd.restore.sh
Normal 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\" }"
|
||||||
63
internal/space_creator/static/dd.sethostname.sh
Normal file
63
internal/space_creator/static/dd.sethostname.sh
Normal 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 }"
|
||||||
15
internal/space_creator/static/editorconfig.txt
Normal file
15
internal/space_creator/static/editorconfig.txt
Normal 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
|
||||||
5
internal/space_creator/static/gitignore.txt
Normal file
5
internal/space_creator/static/gitignore.txt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
# Jet Brains IDEA
|
||||||
|
/.idea/
|
||||||
|
|
||||||
|
# Other
|
||||||
|
*.bak
|
||||||
103
internal/space_worker/backup.go
Normal file
103
internal/space_worker/backup.go
Normal 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
|
||||||
|
}
|
||||||
119
internal/space_worker/host.go
Normal file
119
internal/space_worker/host.go
Normal 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
|
||||||
|
}
|
||||||
71
internal/space_worker/install.go
Normal file
71
internal/space_worker/install.go
Normal 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
|
||||||
|
}
|
||||||
139
internal/space_worker/restore.go
Normal file
139
internal/space_worker/restore.go
Normal 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
|
||||||
|
}
|
||||||
202
internal/space_worker/role.go
Normal file
202
internal/space_worker/role.go
Normal 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
|
||||||
|
}
|
||||||
28
internal/space_worker/space_worker.go
Normal file
28
internal/space_worker/space_worker.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
||||||
4
internal/space_worker/static/README.MD
Normal file
4
internal/space_worker/static/README.MD
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
## Переменные
|
||||||
|
|
||||||
|
| Name | Description | Default |
|
||||||
|
|------|-------------|---------|
|
||||||
48
internal/types/hosts.go
Normal file
48
internal/types/hosts.go
Normal 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"))
|
||||||
|
}
|
||||||
15
internal/types/main_task.go
Normal file
15
internal/types/main_task.go
Normal 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"))
|
||||||
|
}
|
||||||
40
internal/types/playbook.go
Normal file
40
internal/types/playbook.go
Normal 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"))
|
||||||
|
}
|
||||||
60
internal/utils/filesystem.go
Normal file
60
internal/utils/filesystem.go
Normal 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
|
||||||
|
}
|
||||||
39
internal/utils/logger/logger.go
Normal file
39
internal/utils/logger/logger.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
36
internal/utils/yaml_utils.go
Normal file
36
internal/utils/yaml_utils.go
Normal 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)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user