Files
xc/config/config.go
yukra a6359ac0d8 A little customization prompt (#9)
* Add new param in config: show_ssh_threads(bool), show_ssh_threads_min(int) and show_ssh_threads_max(int) for shows this in prompt

* fix CamelCase to lowerCamelCase in cli/cli.go

---------

Co-authored-by: Yuri Kurilkin <y.kurilkin@corp.mail.ru>
2023-03-31 00:12:37 +01:00

409 lines
9.2 KiB
Go

package config
import (
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/chzyer/readline"
"github.com/viert/properties"
)
const defaultConfigContents = `[main]
user =
mode = parallel
history_file = ~/.xc_history
cache_dir = ~/.xc_cache
cache_ttl = 336 # 24 * 7 * 2
rc_file = ~/.xcrc
log_file =
raise = none
exit_confirm = true
exec_confirm = true
distribute = tar
show_ssh_threads = true
show_ssh_threads_min = 5
show_ssh_threads_max = 50
[executer]
ssh_threads = 50
ssh_connect_timeout = 1
ssh_command = /usr/bin/ssh
progress_bar = true
prepend_hostnames = true
remote_tmpdir = /tmp
delay = 0
interpreter = bash
interpreter_sudo = sudo bash
interpreter_su = su -
[ssh]
PasswordAuthentication = no
PubkeyAuthentication = yes
StrictHostKeyChecking = no
TCPKeepAlive = yes
ServerAliveCountMax = 12
ServerAliveInterval = 5
[backend]
type = conductor
url = http://c.inventoree.ru
work_groups =
[passmgr]
path =
`
// BackendType is a backend type enum
type BackendType int
// Backend types
const (
BTIni BackendType = iota
BTConductor
BTInventoree
)
// BackendConfig is a backend configuration struct
type BackendConfig struct {
Type BackendType
TypeString string
Options map[string]string
}
// XCConfig represents a configuration struct for XC
type XCConfig struct {
Readline *readline.Config
BackendCfg *BackendConfig
User string
SSHThreads int
SSHConnectTimeout int
SSHCommand string
SSHOptions map[string]string
RemoteTmpdir string
Mode string
RaiseType string
Delay int
RCfile string
CacheDir string
CacheTTL time.Duration
Debug bool
ProgressBar bool
PrependHostnames bool
LogFile string
ExitConfirm bool
ExecConfirm bool
ShowSsh bool
ShowSshMin int
ShowSshMax int
SudoInterpreter string
SuInterpreter string
Interpreter string
PasswordManagerPath string
PasswordManagerOptions map[string]string
LocalEnvironment map[string]string
RemoteEnvironment map[string]string
Distribute string
}
const (
defaultHistoryFile = "~/.xc_history"
defaultCacheDir = "~/.xc_cache"
defaultRCfile = "~/.xcrc"
defaultCacheTTL = 24
defaultThreads = 50
defaultRemoteTmpDir = "/tmp"
defaultDelay = 0
defaultMode = "parallel"
defaultRaiseType = "none"
defaultDebug = false
defaultProgressbar = true
defaultPrependHostnames = true
defaultSSHConnectTimeout = 1
defaultSSHCommand = "/usr/bin/ssh"
defaultLogFile = ""
defaultExitConfirm = true
defaultExecConfirm = true
defaultShowSshThreads = true
defaultShowSshThreadsMin = 5
defaultShowSshThreadsMax = 50
defaultInterpreter = "/bin/bash"
defaultSudoInterpreter = "sudo /bin/bash"
defaultSuInterpreter = "su -"
defaultDistribute = "tar"
)
var (
defaultReadlineConfig = &readline.Config{
InterruptPrompt: "^C",
EOFPrompt: "exit",
HistorySearchFold: true,
}
defaultUser = os.Getenv("USER")
)
// ExpandPath helper helps to expand ~ as a home directory
// as well as it expands any env variable usage in path
func ExpandPath(path string) string {
if strings.HasPrefix(path, "~/") {
path = "$HOME/" + path[2:]
}
return os.ExpandEnv(path)
}
// Read reads and parses a configuration file
func Read(filename string) (*XCConfig, error) {
return read(filename, false)
}
func read(filename string, secondPass bool) (*XCConfig, error) {
var props *properties.Properties
var err error
props, err = properties.Load(filename)
if err != nil {
if secondPass {
return nil, err
}
if os.IsNotExist(err) {
err = ioutil.WriteFile(filename, []byte(defaultConfigContents), 0644)
if err != nil {
return nil, err
}
}
return read(filename, true)
}
cfg := new(XCConfig)
cfg.Readline = defaultReadlineConfig
cfg.BackendCfg = &BackendConfig{Type: BTIni, Options: make(map[string]string)}
cfg.LocalEnvironment = make(map[string]string)
cfg.RemoteEnvironment = make(map[string]string)
cfg.SSHOptions = make(map[string]string)
hf, err := props.GetString("main.history_file")
if err != nil {
hf = defaultHistoryFile
}
cfg.Readline.HistoryFile = ExpandPath(hf)
rcf, err := props.GetString("main.rc_file")
if err != nil {
rcf = defaultRCfile
}
cfg.RCfile = ExpandPath(rcf)
lf, err := props.GetString("main.log_file")
if err != nil {
lf = defaultLogFile
}
cfg.LogFile = ExpandPath(lf)
cttl, err := props.GetInt("main.cache_ttl")
if err != nil {
cttl = defaultCacheTTL
}
cfg.CacheTTL = time.Hour * time.Duration(cttl)
cd, err := props.GetString("main.cache_dir")
if err != nil {
cd = defaultCacheDir
}
cfg.CacheDir = ExpandPath(cd)
user, err := props.GetString("main.user")
if err != nil || user == "" {
user = defaultUser
}
cfg.User = user
threads, err := props.GetInt("executer.ssh_threads")
if err != nil {
threads = defaultThreads
}
cfg.SSHThreads = threads
sshCommand, err := props.GetString("executer.ssh_command")
if err != nil {
sshCommand = defaultSSHCommand
}
cfg.SSHCommand = sshCommand
ctimeout, err := props.GetInt("executer.ssh_connect_timeout")
if err != nil {
ctimeout = defaultSSHConnectTimeout
}
cfg.SSHConnectTimeout = ctimeout
delay, err := props.GetInt("executer.delay")
if err != nil || delay < 0 {
delay = defaultDelay
}
cfg.Delay = delay
tmpdir, err := props.GetString("executer.remote_tmpdir")
if err != nil {
tmpdir = defaultRemoteTmpDir
}
cfg.RemoteTmpdir = tmpdir
sdi, err := props.GetString("executer.interpreter_sudo")
if err != nil {
sdi = defaultSudoInterpreter
}
cfg.SudoInterpreter = sdi
si, err := props.GetString("executer.interpreter_su")
if err != nil {
si = defaultSuInterpreter
}
cfg.SuInterpreter = si
intrpr, err := props.GetString("executer.interpreter")
if err != nil {
intrpr = defaultInterpreter
}
cfg.Interpreter = intrpr
rt, err := props.GetString("main.raise")
if err != nil {
rt = defaultRaiseType
}
cfg.RaiseType = rt
dtr, err := props.GetString("main.distribute")
if err != nil {
dtr = defaultDistribute
}
cfg.Distribute = dtr
mode, err := props.GetString("main.mode")
if err != nil {
mode = defaultMode
}
cfg.Mode = mode
dbg, err := props.GetBool("main.debug")
if err != nil {
dbg = defaultDebug
}
cfg.Debug = dbg
exitcnfrm, err := props.GetBool("main.exit_confirm")
if err != nil {
exitcnfrm = defaultExitConfirm
}
cfg.ExitConfirm = exitcnfrm
execcnfrm, err := props.GetBool("main.exec_confirm")
if err != nil {
execcnfrm = defaultExecConfirm
}
cfg.ExecConfirm = execcnfrm
shwsshthrds, err := props.GetBool("main.show_ssh_threads")
if err != nil {
shwsshthrds = defaultShowSshThreads
}
cfg.ShowSsh = shwsshthrds
shwsshthrdsmin, err := props.GetInt("main.show_ssh_threads_min")
if err != nil {
shwsshthrdsmin = defaultShowSshThreadsMin
}
cfg.ShowSshMin = shwsshthrdsmin
shwsshthrdsmax, err := props.GetInt("main.show_ssh_threads_max")
if err != nil {
shwsshthrdsmax = defaultShowSshThreadsMax
}
cfg.ShowSshMax = shwsshthrdsmax
pbar, err := props.GetBool("executer.progress_bar")
if err != nil {
pbar = defaultProgressbar
}
cfg.ProgressBar = pbar
phn, err := props.GetBool("executer.prepend_hostnames")
if err != nil {
phn = defaultPrependHostnames
}
cfg.PrependHostnames = phn
sshOptsKeys, err := props.Subkeys("ssh")
if err == nil {
for _, key := range sshOptsKeys {
nsKey := fmt.Sprintf("ssh.%s", key)
cfg.SSHOptions[key], _ = props.GetString(nsKey)
}
}
bkeys, err := props.Subkeys("backend")
if err != nil {
return nil, fmt.Errorf("Backend configuration error: %s", err)
}
typeFound := false
for _, key := range bkeys {
value, _ := props.GetString("backend." + key)
if key == "type" {
cfg.BackendCfg.TypeString = value
switch value {
case "ini":
cfg.BackendCfg.Type = BTIni
case "conductor":
cfg.BackendCfg.Type = BTConductor
case "inventoree":
cfg.BackendCfg.Type = BTInventoree
default:
return nil, fmt.Errorf("Invalid backend type \"%s\"", value)
}
typeFound = true
} else {
cfg.BackendCfg.Options[key] = value
}
}
if !typeFound {
return nil, fmt.Errorf("Error configuring backend: backend type is not defined")
}
cfg.PasswordManagerOptions = make(map[string]string)
if props.KeyExists("passmgr") {
pmgrkeys, err := props.Subkeys("passmgr")
if err == nil {
for _, key := range pmgrkeys {
if key == "path" {
pluginPath, _ := props.GetString("passmgr.path")
cfg.PasswordManagerPath = ExpandPath(pluginPath)
} else {
cfg.PasswordManagerOptions[key], _ = props.GetString(fmt.Sprintf("passmgr.%s", key))
}
}
}
}
envkeys, err := props.Subkeys("environ")
if err == nil {
for _, key := range envkeys {
value, _ := props.GetString(fmt.Sprintf("environ.%s", key))
cfg.LocalEnvironment[key] = ExpandPath(value)
}
}
envkeys, err = props.Subkeys("remote_environ")
if err == nil {
for _, key := range envkeys {
value, _ := props.GetString(fmt.Sprintf("remote_environ.%s", key))
cfg.RemoteEnvironment[key] = value
}
}
return cfg, nil
}