docker-launcher/main.go

224 lines
4.0 KiB
Go

package main
import (
"encoding/json"
"flag"
"log"
"os"
"os/exec"
"regexp"
"strings"
)
type file struct {
Path string
TargetPath string
}
type config struct {
Files []file
VariableRegex string
RegexGroup int
ForceOverwrite bool
UpdateUID bool
User string
UIDVariable string
UpdateGID bool
Group string
GIDVariable string
}
const defaultVariableRegex = `\$\{([a-zA-Z0-9_-]+)\}`
const defaultUIDVariable = "UID"
const defaultGIDVariable = "GID"
func loadConfig(path string) (*config, error) {
raw, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config config
err = json.Unmarshal(raw, &config)
if err != nil {
return nil, err
}
return &config, nil
}
func filterFile(file file, regex regexp.Regexp, regexGroup int) error {
data, err := os.ReadFile(file.Path)
if err != nil {
return err
}
stat, err := os.Stat(file.Path)
if err != nil {
return err
}
text := string(data)
text = regex.ReplaceAllStringFunc(text, func(s string) string {
matches := regex.FindStringSubmatch(text)
return os.Getenv(matches[regexGroup])
})
err = os.WriteFile(file.TargetPath, []byte(text), stat.Mode())
if err != nil {
return err
}
return nil
}
func fileExists(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 filterFiles(config config) error {
var regexString string
if len(config.VariableRegex) == 0 {
regexString = defaultVariableRegex
} else {
regexString = config.VariableRegex
}
var regexGroup int
if config.RegexGroup == 0 {
regexGroup = 1
} else {
regexGroup = config.RegexGroup
}
log.Println("Regex is:", regexString)
regex, err := regexp.Compile(regexString)
if err != nil {
return err
}
for _, file := range config.Files {
if !config.ForceOverwrite {
exists, err := fileExists(file.TargetPath)
if err != nil {
return err
}
if exists {
log.Println("Skipping file:", file.Path, "->", file.TargetPath, "(already exists)")
continue
}
}
log.Println("Filtering file:", file.Path, "->", file.TargetPath)
err := filterFile(file, *regex, regexGroup)
if err != nil {
return err
}
}
return nil
}
func updateUID(config config) error {
uidVariable := config.UIDVariable
if len(uidVariable) == 0 {
uidVariable = defaultUIDVariable
}
newUID := os.Getenv(uidVariable)
if len(newUID) == 0 {
log.Println("UID variable is not set. Not updating UID")
return nil
}
err := exec.Command("usermod", "-u", newUID, config.User).Run()
if err != nil {
return err
}
return nil
}
func updateGID(config config) error {
gidVariable := config.GIDVariable
if len(gidVariable) == 0 {
gidVariable = defaultGIDVariable
}
newGID := os.Getenv(gidVariable)
if len(newGID) == 0 {
log.Println("GID variable is not set. Not updating GID")
return nil
}
err := exec.Command("groupmod", "-g", newGID, config.Group).Run()
if err != nil {
return err
}
return nil
}
func main() {
configPath := flag.String("config", "config.json", "Path to config file")
force := flag.Bool("force", false, "Forcibly overwrite existing files (equivalent to setting forceOverwrite to true in the config)")
flag.Parse()
if flag.NArg() == 0 {
log.Fatal("Missing command")
}
config, err := loadConfig(*configPath)
if err != nil {
log.Fatalln("Failed to load config:", err)
}
config.ForceOverwrite = config.ForceOverwrite || *force
err = filterFiles(*config)
if err != nil {
log.Fatalln(err)
}
if config.UpdateUID {
log.Println("Updating UID")
err = updateUID(*config)
if err != nil {
log.Fatalln(err)
}
}
if config.UpdateGID {
log.Println("Updating GID")
err = updateGID(*config)
if err != nil {
log.Fatalln(err)
}
}
command := flag.Args()
log.Println(">", strings.Join(command, " "))
cmd := exec.Command(command[0], command[1:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
if err != nil {
log.Fatal("Error: ", err)
}
}