2016-02-06 15:23:11 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2018-06-01 15:34:33 +00:00
|
|
|
"fmt"
|
2016-02-06 15:23:11 +00:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
|
2018-06-01 15:34:33 +00:00
|
|
|
log "github.com/sirupsen/logrus"
|
2018-11-02 20:40:47 +00:00
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
"github.com/Luzifer/go_helpers/v2/env"
|
2019-08-04 10:21:14 +00:00
|
|
|
"github.com/Luzifer/rconfig/v2"
|
2016-02-06 15:23:11 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2018-06-01 15:34:33 +00:00
|
|
|
cfg = struct {
|
2018-06-01 15:53:24 +00:00
|
|
|
CleanEnv bool `flag:"clean" default:"false" description:"Do not pass current environment to child process"`
|
2018-11-02 20:40:47 +00:00
|
|
|
EncryptionMethod string `flag:"encryption" default:"openssl-md5" description:"Encryption method used for encrypted env-file (Available: gpg-symmetric, openssl-md5, openssl-sha256)"`
|
2018-06-01 15:53:24 +00:00
|
|
|
EnvFile string `flag:"env-file" default:".env" description:"Location of the environment file"`
|
|
|
|
LogLevel string `flag:"log-level" default:"info" description:"Log level (debug, info, warn, error, fatal)"`
|
|
|
|
PasswordFile string `flag:"password-file" default:"" description:"Read encryption key from file"`
|
|
|
|
Password string `flag:"password,p" default:"" env:"PASSWORD" description:"Password to decrypt environment file"`
|
|
|
|
VersionAndExit bool `flag:"version" default:"false" description:"Prints current version and exits"`
|
2016-02-06 15:23:11 +00:00
|
|
|
}{}
|
2018-06-01 15:34:33 +00:00
|
|
|
|
|
|
|
version = "dev"
|
2016-02-06 15:23:11 +00:00
|
|
|
)
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
func initApp() error {
|
2018-06-01 15:34:33 +00:00
|
|
|
if err := rconfig.ParseAndValidate(&cfg); err != nil {
|
2023-09-30 10:31:07 +00:00
|
|
|
return fmt.Errorf("parsing cli options: %w", err)
|
2018-06-01 15:34:33 +00:00
|
|
|
}
|
2016-02-06 15:23:11 +00:00
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
l, err := log.ParseLevel(cfg.LogLevel)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("parsing log-level: %w", err)
|
2018-06-01 15:34:33 +00:00
|
|
|
}
|
2023-09-30 10:31:07 +00:00
|
|
|
log.SetLevel(l)
|
2018-06-01 15:34:33 +00:00
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
return nil
|
2016-02-06 15:23:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
func main() {
|
|
|
|
var err error
|
2016-02-06 15:23:11 +00:00
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
if err = initApp(); err != nil {
|
|
|
|
log.WithError(err).Fatal("intitializing app")
|
2016-02-06 15:23:11 +00:00
|
|
|
}
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
if cfg.VersionAndExit {
|
|
|
|
fmt.Printf("envrun %s\n", version) //nolint:forbidigo
|
|
|
|
os.Exit(0)
|
2016-02-06 15:23:11 +00:00
|
|
|
}
|
|
|
|
|
2018-06-01 15:37:47 +00:00
|
|
|
if cfg.Password == "" && cfg.PasswordFile != "" {
|
|
|
|
if _, err := os.Stat(cfg.PasswordFile); err == nil {
|
2023-09-30 10:31:07 +00:00
|
|
|
data, err := os.ReadFile(cfg.PasswordFile)
|
2018-06-01 15:37:47 +00:00
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Fatal("Unable to read password from file")
|
|
|
|
}
|
2018-06-01 16:29:19 +00:00
|
|
|
cfg.Password = strings.TrimSpace(string(data))
|
2018-06-01 15:37:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-01 15:53:24 +00:00
|
|
|
dec, err := decryptMethodFromName(cfg.EncryptionMethod)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Fatal("Could not load decrypt method")
|
|
|
|
}
|
|
|
|
|
|
|
|
pairs, err := loadEnvFromFile(cfg.EnvFile, cfg.Password, dec)
|
|
|
|
if err != nil {
|
|
|
|
log.WithError(err).Fatal("Could not load env file")
|
2017-03-21 13:54:59 +00:00
|
|
|
}
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
childenv := env.ListToMap(os.Environ())
|
2016-02-06 15:23:11 +00:00
|
|
|
if cfg.CleanEnv {
|
|
|
|
childenv = map[string]string{}
|
|
|
|
}
|
|
|
|
|
|
|
|
for k, v := range pairs {
|
|
|
|
childenv[k] = v
|
|
|
|
}
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
if len(rconfig.Args()) < 2 { //nolint:gomnd
|
2019-08-04 10:23:52 +00:00
|
|
|
log.Fatal("No command specified")
|
|
|
|
}
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
c := exec.Command(rconfig.Args()[1], rconfig.Args()[2:]...) //#nosec:G204 // Intended to run cmd from input
|
|
|
|
c.Env = env.MapToList(childenv)
|
2016-02-06 15:23:11 +00:00
|
|
|
c.Stdout = os.Stdout
|
|
|
|
c.Stderr = os.Stderr
|
|
|
|
c.Stdin = os.Stdin
|
|
|
|
|
|
|
|
err = c.Run()
|
|
|
|
|
|
|
|
switch err.(type) {
|
|
|
|
case nil:
|
2018-06-01 15:34:33 +00:00
|
|
|
log.Info("Process exitted with code 0")
|
2016-02-06 15:23:11 +00:00
|
|
|
os.Exit(0)
|
|
|
|
case *exec.ExitError:
|
2018-06-01 15:34:33 +00:00
|
|
|
log.Error("Unclean exit with exit-code != 0")
|
2016-02-06 15:23:11 +00:00
|
|
|
os.Exit(1)
|
|
|
|
default:
|
2018-06-01 16:22:53 +00:00
|
|
|
log.WithError(err).Error("An unknown error occurred")
|
2023-09-30 10:31:07 +00:00
|
|
|
os.Exit(2) //nolint:gomnd
|
2016-02-06 15:23:11 +00:00
|
|
|
}
|
|
|
|
}
|
2018-06-01 15:53:24 +00:00
|
|
|
|
|
|
|
func loadEnvFromFile(filename, passphrase string, decrypt decryptMethod) (map[string]string, error) {
|
2023-09-30 10:31:07 +00:00
|
|
|
body, err := os.ReadFile(filename) //#nosec:G304 // Intended to read a variable env file
|
2018-06-01 15:53:24 +00:00
|
|
|
if err != nil {
|
2023-09-30 10:31:07 +00:00
|
|
|
return nil, fmt.Errorf("reading env-file: %w", err)
|
2018-06-01 15:53:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if passphrase != "" {
|
|
|
|
if body, err = decrypt(body, passphrase); err != nil {
|
2023-09-30 10:31:07 +00:00
|
|
|
return nil, fmt.Errorf("decrypting env-file: %w", err)
|
2018-06-01 15:53:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-09-30 10:31:07 +00:00
|
|
|
return env.ListToMap(strings.Split(string(body), "\n")), nil
|
2018-06-01 15:53:24 +00:00
|
|
|
}
|