nomad-var-dirsync/main.go

162 lines
4.1 KiB
Go

package main
import (
"flag"
"fmt"
"io/fs"
"log"
"os"
"path/filepath"
"regexp"
"strconv"
nomad_api "github.com/hashicorp/nomad/api"
)
const DEFAULT_DIR_PERMS = 0o777
var (
invalidPathChars = regexp.MustCompile("[^a-zA-Z0-9-_~/]")
// version of nomad-var-dirsync being run, set with ldflags
version = "dev"
)
func writeDir(client *nomad_api.Client, root string, sourceDir string) error {
err := filepath.WalkDir(sourceDir, func(path string, dir fs.DirEntry, err error) error {
if err != nil {
return fmt.Errorf("could not walk to %s: %w", path, err)
}
if dir.IsDir() {
return nil
}
fileInfo, err := dir.Info()
if err != nil {
return fmt.Errorf("failed getting info for %s: %w", path, err)
}
contents, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("failed reading file %s: %w", path, err)
}
sanitizedPath := invalidPathChars.ReplaceAllString(path, "_")
sanitizedPath = filepath.Join(root, sanitizedPath)
log.Printf("[INFO] Writing variable %s", sanitizedPath)
newVar := nomad_api.Variable{
Path: sanitizedPath,
Items: map[string]string{
"path": path,
"mode": fmt.Sprintf("%o", fileInfo.Mode()),
"contents": string(contents),
},
}
if _, _, err := client.Variables().Create(&newVar, nil); err != nil {
return fmt.Errorf("failed creating var %s for file %s: %w", sanitizedPath, path, err)
}
return nil
})
if err != nil {
return fmt.Errorf("Error walking dir %s: %w", sourceDir, err)
}
return nil
}
func readDir(client *nomad_api.Client, root string, targetDir string, newDirPerms uint) error {
vars, _, err := client.Variables().List(&nomad_api.QueryOptions{
Prefix: root,
})
if err != nil {
return fmt.Errorf("failed reading vars from root %s: %w", root, err)
}
for _, varInfo := range vars {
log.Printf("[INFO] Reading variable %s", varInfo.Path)
fileVar, _, err := client.Variables().Read(varInfo.Path, &nomad_api.QueryOptions{})
if err != nil {
log.Printf("[ERROR] Failed reading variable %s: %v", varInfo.Path, err)
}
filePath := filepath.Join(targetDir, fileVar.Items["path"])
fileModeString := fileVar.Items["mode"]
fileContents := fileVar.Items["contents"]
fileMode, err := strconv.ParseUint(fileModeString, 8, 32)
if err != nil {
return fmt.Errorf("Failed parsing file mode for %s. %s: %w", filePath, fileModeString, err)
}
parentDir := filepath.Dir(filePath)
if _, err := os.Stat(parentDir); err != nil {
if err = os.MkdirAll(parentDir, fs.FileMode(newDirPerms)); err != nil {
return fmt.Errorf("error creating paretn dir for file at path %s: %w", filePath, err)
}
}
err = os.WriteFile(filePath, []byte(fileContents), os.FileMode(fileMode))
if err != nil {
return fmt.Errorf("Failed writing file %s: %w", filePath, err)
}
}
return nil
}
func main() {
root := flag.String("root-var", "", "root path for nomad variable")
showVersion := flag.Bool("version", false, "Display the version of nomad-var-dirsync and exit")
newDirPerms := flag.Uint("dir-perms", DEFAULT_DIR_PERMS, "default permissions for new directories (default: 0o777)")
flag.Parse()
// Print version if flag is provided
if *showVersion {
fmt.Println("nomad-var-dirsync version:", version)
return
}
action := flag.Arg(0)
target := flag.Arg(1)
if *root == "" {
log.Fatal("[ERROR] Must provide a nomad variable root -root-var")
}
targetStat, err := os.Stat(target)
if err != nil {
log.Fatalf("[ERROR] Failed reading target file `%s`. %v", target, err)
}
if !targetStat.IsDir() {
log.Fatalf("[ERROR] Must provide a path to a directory: %s", target)
}
client, err := nomad_api.NewClient(&nomad_api.Config{
SecretID: os.Getenv("NOMAD_TOKEN"),
})
if err != nil {
log.Fatalf("[ERROR] Failed creating nomad client: %v", err)
}
switch action {
case "write":
if err = writeDir(client, *root, target); err != nil {
log.Fatalf("[ERROR] Failed writing directory: %v", err)
}
case "read":
if err = readDir(client, *root, target, *newDirPerms); err != nil {
log.Fatalf("[ERROR] Failed reading to files for path %v", err)
}
default:
log.Fatalf("[ERROR] Expected action read or write, found %s", action)
}
}