package config import ( "bufio" "fmt" "io/fs" "io/ioutil" "os" "path" "path/filepath" "regexp" "sort" "strings" "git.coopcloud.tech/coop-cloud/godotenv" "github.com/sirupsen/logrus" ) // getBaseDir retrieves the Abra base directory. func getBaseDir() string { home := os.ExpandEnv("$HOME/.abra") if customAbraDir, exists := os.LookupEnv("ABRA_DIR"); exists && customAbraDir != "" { home = customAbraDir } return home } var ABRA_DIR = getBaseDir() var SERVERS_DIR = path.Join(ABRA_DIR, "servers") var RECIPES_DIR = path.Join(ABRA_DIR, "recipes") var VENDOR_DIR = path.Join(ABRA_DIR, "vendor") var BACKUP_DIR = path.Join(ABRA_DIR, "backups") var CATALOGUE_DIR = path.Join(ABRA_DIR, "catalogue") var RECIPES_JSON = path.Join(ABRA_DIR, "catalogue", "recipes.json") var REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud" var CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json" var SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git" var BackupbotLabel = "coop-cloud.backupbot.enabled" // envVarModifiers is a list of env var modifier strings. These are added to // env vars as comments and modify their processing by Abra, e.g. determining // how long secrets should be. var envVarModifiers = []string{"length"} // GetServers retrieves all servers. func GetServers() ([]string, error) { var servers []string servers, err := GetAllFoldersInDirectory(SERVERS_DIR) if err != nil { return servers, err } logrus.Debugf("retrieved %v servers: %s", len(servers), servers) return servers, nil } // ReadEnv loads an app envivornment into a map. func ReadEnv(filePath string) (AppEnv, error) { var envVars AppEnv envVars, _, err := godotenv.Read(filePath) if err != nil { return nil, err } logrus.Debugf("read %s from %s", envVars, filePath) return envVars, nil } // ReadEnv loads an app envivornment and their modifiers in two different maps. func ReadEnvWithModifiers(filePath string) (AppEnv, AppModifiers, error) { var envVars AppEnv envVars, mods, err := godotenv.Read(filePath) if err != nil { return nil, mods, err } logrus.Debugf("read %s from %s", envVars, filePath) return envVars, mods, nil } // ReadServerNames retrieves all server names. func ReadServerNames() ([]string, error) { serverNames, err := GetAllFoldersInDirectory(SERVERS_DIR) if err != nil { return nil, err } logrus.Debugf("read %s from %s", strings.Join(serverNames, ","), SERVERS_DIR) return serverNames, nil } // GetAllFilesInDirectory returns filenames of all files in directory func GetAllFilesInDirectory(directory string) ([]fs.FileInfo, error) { var realFiles []fs.FileInfo files, err := ioutil.ReadDir(directory) if err != nil { return nil, err } for _, file := range files { // Follow any symlinks filePath := path.Join(directory, file.Name()) if filepath.Ext(strings.TrimSpace(filePath)) != ".env" { continue } realPath, err := filepath.EvalSymlinks(filePath) if err != nil { logrus.Warningf("broken symlink in your abra config folders: %s", filePath) } else { realFile, err := os.Stat(realPath) if err != nil { return nil, err } if !realFile.IsDir() { realFiles = append(realFiles, file) } } } return realFiles, nil } // GetAllFoldersInDirectory returns both folder and symlink paths func GetAllFoldersInDirectory(directory string) ([]string, error) { var folders []string files, err := ioutil.ReadDir(directory) if err != nil { return nil, err } if len(files) == 0 { return nil, fmt.Errorf("directory is empty: %s", directory) } for _, file := range files { // Check if file is directory or symlink if file.IsDir() || file.Mode()&fs.ModeSymlink != 0 { filePath := path.Join(directory, file.Name()) realDir, err := filepath.EvalSymlinks(filePath) if err != nil { logrus.Warningf("broken symlink in your abra config folders: %s", filePath) } else if stat, err := os.Stat(realDir); err == nil && stat.IsDir() { // path is a directory folders = append(folders, file.Name()) } } } return folders, nil } // ReadAbraShEnvVars reads env vars from an abra.sh recipe file. func ReadAbraShEnvVars(abraSh string) (map[string]string, error) { envVars := make(map[string]string) file, err := os.Open(abraSh) if err != nil { if os.IsNotExist(err) { return envVars, nil } return envVars, err } defer file.Close() exportRegex, err := regexp.Compile(`^export\s+(\w+=\w+)`) if err != nil { return envVars, err } scanner := bufio.NewScanner(file) for scanner.Scan() { txt := scanner.Text() if exportRegex.MatchString(txt) { splitVals := strings.Split(txt, "export ") envVarDef := splitVals[len(splitVals)-1] keyVal := strings.Split(envVarDef, "=") if len(keyVal) != 2 { return envVars, fmt.Errorf("couldn't parse %s", txt) } envVars[keyVal[0]] = keyVal[1] } } if len(envVars) > 0 { logrus.Debugf("read %s from %s", envVars, abraSh) } else { logrus.Debugf("read 0 env var exports from %s", abraSh) } return envVars, nil } type EnvVar struct { Name string Present bool } func CheckEnv(app App) ([]EnvVar, error) { var envVars []EnvVar envSamplePath := path.Join(RECIPES_DIR, app.Recipe, ".env.sample") if _, err := os.Stat(envSamplePath); err != nil { if os.IsNotExist(err) { return envVars, fmt.Errorf("%s does not exist?", envSamplePath) } return envVars, err } envSample, err := ReadEnv(envSamplePath) if err != nil { return envVars, err } var keys []string for key := range envSample { keys = append(keys, key) } sort.Strings(keys) for _, key := range keys { if _, ok := app.Env[key]; ok { envVars = append(envVars, EnvVar{Name: key, Present: true}) } else { envVars = append(envVars, EnvVar{Name: key, Present: false}) } } return envVars, nil } // ReadAbraShCmdNames reads the names of commands. func ReadAbraShCmdNames(abraSh string) ([]string, error) { var cmdNames []string file, err := os.Open(abraSh) if err != nil { if os.IsNotExist(err) { return cmdNames, nil } return cmdNames, err } defer file.Close() cmdNameRegex, err := regexp.Compile(`(\w+)(\(\).*\{)`) if err != nil { return cmdNames, err } scanner := bufio.NewScanner(file) for scanner.Scan() { line := scanner.Text() matches := cmdNameRegex.FindStringSubmatch(line) if len(matches) > 0 { cmdNames = append(cmdNames, matches[1]) } } if len(cmdNames) > 0 { logrus.Debugf("read %s from %s", strings.Join(cmdNames, " "), abraSh) } else { logrus.Debugf("read 0 command names from %s", abraSh) } return cmdNames, nil }