|
|
|
@ -7,15 +7,19 @@ import (
|
|
|
|
|
"io"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
|
"github.com/rs/zerolog"
|
|
|
|
|
"github.com/spf13/viper"
|
|
|
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Host defines a host to which to connect
|
|
|
|
|
// If not provided, the values will be looked up in the default ssh config files
|
|
|
|
|
type Host struct {
|
|
|
|
|
ConfigFilePath string
|
|
|
|
|
UseConfigFile bool
|
|
|
|
|
Empty bool
|
|
|
|
|
Host string
|
|
|
|
|
HostName string
|
|
|
|
@ -26,44 +30,55 @@ type Host struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type Command struct {
|
|
|
|
|
Remote bool
|
|
|
|
|
Remote bool `yaml:"remote,omitempty"`
|
|
|
|
|
|
|
|
|
|
// command to run
|
|
|
|
|
Cmd string
|
|
|
|
|
Cmd string `yaml:"cmd"`
|
|
|
|
|
|
|
|
|
|
// host on which to run cmd
|
|
|
|
|
Host string
|
|
|
|
|
Host *string `yaml:"host,omitempty"`
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Shell specifies which shell to run the command in, if any
|
|
|
|
|
Not applicable when host is defined
|
|
|
|
|
Shell specifies which shell to run the command in, if any.
|
|
|
|
|
Not applicable when host is defined.
|
|
|
|
|
*/
|
|
|
|
|
Shell string
|
|
|
|
|
Shell string `yaml:"shell,omitempty"`
|
|
|
|
|
|
|
|
|
|
RemoteHost Host
|
|
|
|
|
RemoteHost Host `yaml:"-"`
|
|
|
|
|
|
|
|
|
|
// cmdArgs is an array that holds the arguments to cmd
|
|
|
|
|
CmdArgs []string
|
|
|
|
|
CmdArgs []string `yaml:"cmdArgs,omitempty"`
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Dir specifies a directory in which to run the command.
|
|
|
|
|
Ignored if Host is set.
|
|
|
|
|
*/
|
|
|
|
|
Dir *string `yaml:"dir,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BackyGlobalOpts struct {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BackyConfigFile struct {
|
|
|
|
|
/*
|
|
|
|
|
Cmds holds the commands for a list
|
|
|
|
|
key is the name of the command
|
|
|
|
|
Cmds holds the commands for a list.
|
|
|
|
|
Key is the name of the command,
|
|
|
|
|
*/
|
|
|
|
|
Cmds map[string]Command
|
|
|
|
|
Cmds map[string]Command `yaml:"commands"`
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
CmdLists holds the lists of commands to be run in order
|
|
|
|
|
key is the command list name
|
|
|
|
|
CmdLists holds the lists of commands to be run in order.
|
|
|
|
|
Key is the command list name.
|
|
|
|
|
*/
|
|
|
|
|
CmdLists map[string][]string
|
|
|
|
|
CmdLists map[string][]string `yaml:"cmd-lists"`
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
Hosts holds the Host config
|
|
|
|
|
key is the host
|
|
|
|
|
Hosts holds the Host config.
|
|
|
|
|
key is the host.
|
|
|
|
|
*/
|
|
|
|
|
Hosts map[string]Host
|
|
|
|
|
Hosts map[string]Host `yaml:"hosts"`
|
|
|
|
|
|
|
|
|
|
Logger zerolog.Logger
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// BackupConfig is a configuration struct that is used to define backups
|
|
|
|
@ -79,19 +94,19 @@ type BackupConfig struct {
|
|
|
|
|
* Runs a backup configuration
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
func (command Command) RunCmd() {
|
|
|
|
|
func (command Command) RunCmd(log *zerolog.Logger) {
|
|
|
|
|
|
|
|
|
|
var cmdArgsStr string
|
|
|
|
|
for _, v := range command.CmdArgs {
|
|
|
|
|
cmdArgsStr += fmt.Sprintf(" %s", v)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Printf("\n\nRunning command: " + command.Cmd + " " + cmdArgsStr + " on host " + command.Host + "...\n\n")
|
|
|
|
|
if command.Host != "" {
|
|
|
|
|
fmt.Printf("\n\nRunning command: " + command.Cmd + " " + cmdArgsStr + " on host " + *command.Host + "...\n\n")
|
|
|
|
|
if command.Host != nil {
|
|
|
|
|
|
|
|
|
|
command.RemoteHost.Host = command.Host
|
|
|
|
|
command.RemoteHost.Host = *command.Host
|
|
|
|
|
command.RemoteHost.Port = 22
|
|
|
|
|
sshc, err := command.RemoteHost.ConnectToSSHHost()
|
|
|
|
|
sshc, err := command.RemoteHost.ConnectToSSHHost(log)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(fmt.Errorf("ssh dial: %w", err))
|
|
|
|
|
}
|
|
|
|
@ -123,6 +138,9 @@ func (command Command) RunCmd() {
|
|
|
|
|
if command.Shell != "" {
|
|
|
|
|
cmdArgsStr = fmt.Sprintf("%s %s", command.Cmd, cmdArgsStr)
|
|
|
|
|
localCMD := exec.Command(command.Shell, "-c", cmdArgsStr)
|
|
|
|
|
if command.Dir != nil {
|
|
|
|
|
localCMD.Dir = *command.Dir
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var stdoutBuf, stderrBuf bytes.Buffer
|
|
|
|
|
localCMD.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
|
|
|
@ -137,7 +155,9 @@ func (command Command) RunCmd() {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
localCMD := exec.Command(command.Cmd, command.CmdArgs...)
|
|
|
|
|
|
|
|
|
|
if command.Dir != nil {
|
|
|
|
|
localCMD.Dir = *command.Dir
|
|
|
|
|
}
|
|
|
|
|
var stdoutBuf, stderrBuf bytes.Buffer
|
|
|
|
|
localCMD.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
|
|
|
|
|
localCMD.Stderr = io.MultiWriter(os.Stderr, &stderrBuf)
|
|
|
|
@ -154,17 +174,49 @@ func (config *BackyConfigFile) RunBackyConfig() {
|
|
|
|
|
for _, list := range config.CmdLists {
|
|
|
|
|
for _, cmd := range list {
|
|
|
|
|
cmdToRun := config.Cmds[cmd]
|
|
|
|
|
cmdToRun.RunCmd()
|
|
|
|
|
cmdToRun.RunCmd(&config.Logger)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BackyConfigOpts struct {
|
|
|
|
|
// Holds config file
|
|
|
|
|
ConfigFile *BackyConfigFile
|
|
|
|
|
// Holds config file
|
|
|
|
|
ConfigFilePath string
|
|
|
|
|
|
|
|
|
|
// Global log level
|
|
|
|
|
BackyLogLvl *string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type BackyOptionFunc func(*BackyConfigOpts)
|
|
|
|
|
|
|
|
|
|
func (c *BackyConfigOpts) LogLvl(level string) BackyOptionFunc {
|
|
|
|
|
return func(bco *BackyConfigOpts) {
|
|
|
|
|
c.BackyLogLvl = &level
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
func (c *BackyConfigOpts) GetConfig() {
|
|
|
|
|
c.ConfigFile = ReadAndParseConfigFile(c.ConfigFilePath)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func New() BackupConfig {
|
|
|
|
|
return BackupConfig{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewConfig initializes new config that holds information
|
|
|
|
|
// from the config file
|
|
|
|
|
func NewOpts(configFilePath string, opts ...BackyOptionFunc) *BackyConfigOpts {
|
|
|
|
|
b := &BackyConfigOpts{}
|
|
|
|
|
b.ConfigFilePath = configFilePath
|
|
|
|
|
for _, opt := range opts {
|
|
|
|
|
opt(b)
|
|
|
|
|
}
|
|
|
|
|
return b
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* NewConfig initializes new config that holds information
|
|
|
|
|
* from the config file
|
|
|
|
|
*/
|
|
|
|
|
func NewConfig() *BackyConfigFile {
|
|
|
|
|
return &BackyConfigFile{
|
|
|
|
|
Cmds: make(map[string]Command),
|
|
|
|
@ -173,21 +225,89 @@ func NewConfig() *BackyConfigFile {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func ReadAndParseConfigFile() *BackyConfigFile {
|
|
|
|
|
func ReadAndParseConfigFile(configFile string) *BackyConfigFile {
|
|
|
|
|
|
|
|
|
|
backyConfigFile := NewConfig()
|
|
|
|
|
|
|
|
|
|
backyViper := viper.New()
|
|
|
|
|
|
|
|
|
|
if configFile != "" {
|
|
|
|
|
backyViper.SetConfigFile(configFile)
|
|
|
|
|
} else {
|
|
|
|
|
backyViper.SetConfigName("backy") // name of config file (without extension)
|
|
|
|
|
backyViper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name
|
|
|
|
|
backyViper.AddConfigPath(".") // optionally look for config in the working directory
|
|
|
|
|
backyViper.AddConfigPath("$HOME/.config/backy") // call multiple times to add many search paths
|
|
|
|
|
}
|
|
|
|
|
err := backyViper.ReadInConfig() // Find and read the config file
|
|
|
|
|
if err != nil { // Handle errors reading the config file
|
|
|
|
|
panic(fmt.Errorf("fatal error config file: %w", err))
|
|
|
|
|
panic(fmt.Errorf("fatal error finding config file: %w", err))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
backyLoggingOpts := backyViper.Sub("logging")
|
|
|
|
|
verbose := backyLoggingOpts.GetBool("verbose")
|
|
|
|
|
|
|
|
|
|
logFile := backyLoggingOpts.GetString("file")
|
|
|
|
|
if verbose {
|
|
|
|
|
zerolog.Level.String(zerolog.DebugLevel)
|
|
|
|
|
}
|
|
|
|
|
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC1123}
|
|
|
|
|
output.FormatLevel = func(i interface{}) string {
|
|
|
|
|
return strings.ToUpper(fmt.Sprintf("| %-6s|", i))
|
|
|
|
|
}
|
|
|
|
|
output.FormatMessage = func(i interface{}) string {
|
|
|
|
|
return fmt.Sprintf("%s", i)
|
|
|
|
|
}
|
|
|
|
|
output.FormatFieldName = func(i interface{}) string {
|
|
|
|
|
return fmt.Sprintf("%s: ", i)
|
|
|
|
|
}
|
|
|
|
|
output.FormatFieldValue = func(i interface{}) string {
|
|
|
|
|
return strings.ToUpper(fmt.Sprintf("%s", i))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fileLogger := &lumberjack.Logger{
|
|
|
|
|
MaxSize: 500, // megabytes
|
|
|
|
|
MaxBackups: 3,
|
|
|
|
|
MaxAge: 28, //days
|
|
|
|
|
Compress: true, // disabled by default
|
|
|
|
|
}
|
|
|
|
|
if strings.Trim(logFile, " ") != "" {
|
|
|
|
|
fileLogger.Filename = logFile
|
|
|
|
|
} else {
|
|
|
|
|
fileLogger.Filename = "./backy.log"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UNIX Time is faster and smaller than most timestamps
|
|
|
|
|
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix
|
|
|
|
|
// zerolog.TimeFieldFormat = time.RFC1123
|
|
|
|
|
writers := zerolog.MultiLevelWriter(os.Stdout, fileLogger)
|
|
|
|
|
log := zerolog.New(writers).With().Timestamp().Logger()
|
|
|
|
|
|
|
|
|
|
backyConfigFile.Logger = log
|
|
|
|
|
|
|
|
|
|
commandsMap := backyViper.GetStringMapString("commands")
|
|
|
|
|
commandsMapViper := backyViper.Sub("commands")
|
|
|
|
|
unmarshalErr := commandsMapViper.Unmarshal(&backyConfigFile.Cmds)
|
|
|
|
|
if unmarshalErr != nil {
|
|
|
|
|
panic(fmt.Errorf("error unmarshalling cmds struct: %w", unmarshalErr))
|
|
|
|
|
} else {
|
|
|
|
|
for cmdName, cmdConf := range backyConfigFile.Cmds {
|
|
|
|
|
fmt.Printf("\nCommand Name: %s\n", cmdName)
|
|
|
|
|
fmt.Printf("Shell: %v\n", cmdConf.Shell)
|
|
|
|
|
fmt.Printf("Command: %s\n", cmdConf.Cmd)
|
|
|
|
|
|
|
|
|
|
if len(cmdConf.CmdArgs) > 0 {
|
|
|
|
|
fmt.Println("\nCmd Args:")
|
|
|
|
|
for _, args := range cmdConf.CmdArgs {
|
|
|
|
|
fmt.Printf("%s\n", args)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if cmdConf.Host != nil {
|
|
|
|
|
fmt.Printf("Host: %s\n", *backyConfigFile.Cmds[cmdName].Host)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
os.Exit(0)
|
|
|
|
|
}
|
|
|
|
|
var cmdNames []string
|
|
|
|
|
for k := range commandsMap {
|
|
|
|
|
cmdNames = append(cmdNames, k)
|
|
|
|
@ -196,63 +316,30 @@ func ReadAndParseConfigFile() *BackyConfigFile {
|
|
|
|
|
|
|
|
|
|
for _, cmdName := range cmdNames {
|
|
|
|
|
var backupCmdStruct Command
|
|
|
|
|
println(cmdName)
|
|
|
|
|
subCmd := backyViper.Sub(getNestedConfig("commands", cmdName))
|
|
|
|
|
|
|
|
|
|
hostSet := subCmd.IsSet("host")
|
|
|
|
|
host := subCmd.GetString("host")
|
|
|
|
|
|
|
|
|
|
cmdSet := subCmd.IsSet("cmd")
|
|
|
|
|
cmd := subCmd.GetString("cmd")
|
|
|
|
|
cmdArgsSet := subCmd.IsSet("cmdargs")
|
|
|
|
|
cmdArgs := subCmd.GetStringSlice("cmdargs")
|
|
|
|
|
shellSet := subCmd.IsSet("shell")
|
|
|
|
|
shell := subCmd.GetString("shell")
|
|
|
|
|
|
|
|
|
|
if hostSet {
|
|
|
|
|
println("Host:")
|
|
|
|
|
println(host)
|
|
|
|
|
backupCmdStruct.Host = host
|
|
|
|
|
log.Debug().Timestamp().Str(cmdName, "host is set").Str("host", host).Send()
|
|
|
|
|
backupCmdStruct.Host = &host
|
|
|
|
|
if backyViper.IsSet(getNestedConfig("hosts", host)) {
|
|
|
|
|
hostconfig := backyViper.Sub(getNestedConfig("hosts", host))
|
|
|
|
|
hostConfigsMap[host] = hostconfig
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
println("Host is not set")
|
|
|
|
|
}
|
|
|
|
|
if cmdSet {
|
|
|
|
|
println("Cmd:")
|
|
|
|
|
println(cmd)
|
|
|
|
|
backupCmdStruct.Cmd = cmd
|
|
|
|
|
} else {
|
|
|
|
|
println("Cmd is not set")
|
|
|
|
|
}
|
|
|
|
|
if shellSet {
|
|
|
|
|
println("Shell:")
|
|
|
|
|
println(shell)
|
|
|
|
|
backupCmdStruct.Shell = shell
|
|
|
|
|
} else {
|
|
|
|
|
println("Shell is not set")
|
|
|
|
|
}
|
|
|
|
|
if cmdArgsSet {
|
|
|
|
|
println("CmdArgs:")
|
|
|
|
|
for _, arg := range cmdArgs {
|
|
|
|
|
println(arg)
|
|
|
|
|
log.Debug().Timestamp().Str(cmdName, "host is not set").Send()
|
|
|
|
|
}
|
|
|
|
|
backupCmdStruct.CmdArgs = cmdArgs
|
|
|
|
|
} else {
|
|
|
|
|
println("CmdArgs are not set")
|
|
|
|
|
}
|
|
|
|
|
backyConfigFile.Cmds[cmdName] = backupCmdStruct
|
|
|
|
|
|
|
|
|
|
// backyConfigFile.Cmds[cmdName] = backupCmdStruct
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmdListCfg := backyViper.GetStringMapStringSlice("cmd-lists")
|
|
|
|
|
var cmdNotFoundSliceErr []error
|
|
|
|
|
for cmdListName, cmdList := range cmdListCfg {
|
|
|
|
|
println("Cmd list: ", cmdListName)
|
|
|
|
|
for _, cmdInList := range cmdList {
|
|
|
|
|
println("Command in list: " + cmdInList)
|
|
|
|
|
_, cmdNameFound := backyConfigFile.Cmds[cmdInList]
|
|
|
|
|
if !backyViper.IsSet(getNestedConfig("commands", cmdInList)) && !cmdNameFound {
|
|
|
|
|
cmdNotFoundStr := fmt.Sprintf("command definition %s is not in config file\n", cmdInList)
|
|
|
|
|