chore(gonf): replace global variables with an env struct

This commit is contained in:
Julien Dessaux 2024-06-12 23:24:52 +02:00
parent 07db4ab5bd
commit da6565d5f7
Signed by: adyxax
GPG key ID: F92E51B86E07177E
6 changed files with 105 additions and 112 deletions

View file

@ -1,38 +1,31 @@
package main
import (
"context"
"flag"
"fmt"
"io"
"os"
"os/exec"
)
func cmdBuild(ctx context.Context,
f *flag.FlagSet,
args []string,
getenv func(string) string,
stdout, stderr io.Writer,
) error {
f.Init(`gonf build [-FLAG]
func (env *Env) cmdBuild() error {
env.flagSet.Init(`gonf build [-FLAG]
where FLAG can be one or more of`, flag.ContinueOnError)
hostFlag := addHostFlag(f)
f.SetOutput(stderr)
f.Parse(args)
if helpMode {
f.SetOutput(stdout)
f.Usage()
hostFlag := env.addHostFlag()
env.flagSet.SetOutput(env.stderr)
env.flagSet.Parse(env.args)
if env.helpMode {
env.flagSet.SetOutput(env.stdout)
env.flagSet.Usage()
}
hostDir, err := hostFlagToHostDir(hostFlag, getenv)
hostDir, err := env.hostFlagToHostDir(hostFlag)
if err != nil {
f.Usage()
env.flagSet.Usage()
return err
}
return runBuild(ctx, stderr, hostDir)
return env.runBuild(hostDir)
}
func runBuild(ctx context.Context, stderr io.Writer, hostDir string) error {
func (env *Env) runBuild(hostDir string) error {
wd, err := os.Getwd()
if err != nil {
return err
@ -41,10 +34,10 @@ func runBuild(ctx context.Context, stderr io.Writer, hostDir string) error {
if err = os.Chdir(hostDir); err != nil {
return err
}
cmd := exec.CommandContext(ctx, "go", "build", "-ldflags", "-s -w -extldflags \"-static\"", hostDir)
cmd := exec.CommandContext(env.ctx, "go", "build", "-ldflags", "-s -w -extldflags \"-static\"", hostDir)
cmd.Env = append(cmd.Environ(), "CGO_ENABLED=0")
if out, err := cmd.CombinedOutput(); err != nil {
_, _ = fmt.Fprint(stderr, string(out))
_, _ = fmt.Fprint(env.stderr, string(out))
return err
}
return nil

View file

@ -1,43 +1,31 @@
package main
import (
"context"
"flag"
"io"
"log/slog"
"path/filepath"
)
func cmdDeploy(ctx context.Context,
f *flag.FlagSet,
args []string,
getenv func(string) string,
stdout, stderr io.Writer,
) error {
f.Init(`gonf deploy [-FLAG]
func (env *Env) cmdDeploy() error {
env.flagSet.Init(`gonf deploy [-FLAG]
where FLAG can be one or more of`, flag.ContinueOnError)
hostFlag := addHostFlag(f)
f.SetOutput(stderr)
f.Parse(args)
if helpMode {
f.SetOutput(stdout)
f.Usage()
hostFlag := env.addHostFlag()
env.flagSet.SetOutput(env.stderr)
env.flagSet.Parse(env.args)
if env.helpMode {
env.flagSet.SetOutput(env.stdout)
env.flagSet.Usage()
}
hostDir, err := hostFlagToHostDir(hostFlag, getenv)
hostDir, err := env.hostFlagToHostDir(hostFlag)
if err != nil {
f.Usage()
env.flagSet.Usage()
return err
}
return runDeploy(ctx, getenv, stdout, stderr, *hostFlag, hostDir)
return env.runDeploy(*hostFlag, hostDir)
}
func runDeploy(ctx context.Context,
getenv func(string) string,
stdout, stderr io.Writer,
hostFlag string,
hostDir string,
) error {
sshc, err := newSSHClient(ctx, getenv, hostFlag+":22")
func (env *Env) runDeploy(hostFlag string, hostDir string) error {
sshc, err := env.newSSHClient(hostFlag + ":22")
if err != nil {
slog.Error("deploy", "action", "newSshClient", "error", err)
return err
@ -48,7 +36,7 @@ func runDeploy(ctx context.Context,
}
}()
if err = sshc.SendFile(ctx, stdout, stderr, filepath.Join(hostDir, hostFlag)); err != nil {
if err = sshc.SendFile(filepath.Join(hostDir, hostFlag)); err != nil {
slog.Error("deploy", "action", "SendFile", "error", err)
}

19
cmd/gonf/env.go Normal file
View file

@ -0,0 +1,19 @@
package main
import (
"context"
"flag"
"io"
)
type Env struct {
batchMode bool
ctx context.Context
configDir string
flagSet *flag.FlagSet
helpMode bool
args []string
getenv func(string) string
stdout io.Writer
stderr io.Writer
}

View file

@ -1,26 +1,23 @@
package main
import (
"flag"
"fmt"
"os"
"path/filepath"
)
func addHostFlag(f *flag.FlagSet) *string {
return f.String("host", "", "(REQUIRED) a valid $GONF_CONFIG/hosts/ subdirectory (overrides the GONF_HOST environment variable)")
func (env *Env) addHostFlag() *string {
return env.flagSet.String("host", "", "(REQUIRED) a valid $GONF_CONFIG/hosts/ subdirectory (overrides the GONF_HOST environment variable)")
}
func hostFlagToHostDir(hostFlag *string,
getenv func(string) string,
) (string, error) {
func (env *Env) hostFlagToHostDir(hostFlag *string) (string, error) {
if *hostFlag == "" {
*hostFlag = getenv("GONF_HOST")
*hostFlag = env.getenv("GONF_HOST")
if *hostFlag == "" {
return "", fmt.Errorf("the GONF_HOST environment variable is unset and the -host FLAG is missing. Please use one or the other")
}
}
hostDir := filepath.Join(configDir, "hosts", *hostFlag)
hostDir := filepath.Join(env.configDir, "hosts", *hostFlag)
if info, err := os.Stat(hostDir); err != nil {
return "", fmt.Errorf("invalid host name %s: %w", *hostFlag, err)
} else if !info.IsDir() {

View file

@ -4,80 +4,72 @@ import (
"context"
"flag"
"fmt"
"io"
"os"
"os/signal"
)
var (
batchMode bool
configDir string
helpMode bool
)
func main() {
if err := run(context.Background(),
os.Args,
os.Getenv,
//os.Getwd,
//os.Stdin,
os.Stdout,
os.Stderr,
); err != nil {
env := Env{
batchMode: false,
ctx: context.Background(),
configDir: "",
flagSet: nil,
helpMode: false,
args: os.Args,
getenv: os.Getenv,
stdout: os.Stdout,
stderr: os.Stderr,
}
if err := env.run(); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err)
os.Exit(1)
}
}
func run(ctx context.Context,
args []string,
getenv func(string) string,
//getwd func() (string, error),
//stdin io.Reader,
stdout, stderr io.Writer,
) error {
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
func (env *Env) run() error {
ctx, cancel := signal.NotifyContext(env.ctx, os.Interrupt)
defer cancel()
f := flag.NewFlagSet(`gonf COMMAND [-FLAG]
env.ctx = ctx
env.flagSet = flag.NewFlagSet(`gonf COMMAND [-FLAG]
where COMMAND is one of:
* build: build configuration for a host
* deploy: deploy configuration for a host
* help: show contextual help
* version: show build version and time
where FLAG can be one or more of`, flag.ContinueOnError)
f.BoolVar(&batchMode, "batch", false, "skips all questions and confirmations, using the default (safe) choices each time")
f.BoolVar(&helpMode, "help", false, "show contextual help")
f.StringVar(&configDir, "config", "", "(REQUIRED for most commands) path to a gonf configurations repository (overrides the GONF_CONFIG environment variable)")
f.SetOutput(stderr)
f.Parse(args[1:])
env.flagSet.BoolVar(&env.batchMode, "batch", false, "skips all questions and confirmations, using the default (safe) choices each time")
env.flagSet.BoolVar(&env.helpMode, "help", false, "show contextual help")
env.flagSet.StringVar(&env.configDir, "config", "", "(REQUIRED for most commands) path to a gonf configurations repository (overrides the GONF_CONFIG environment variable)")
env.flagSet.SetOutput(env.stderr)
env.flagSet.Parse(env.args[1:])
if f.NArg() < 1 {
f.Usage()
if env.flagSet.NArg() < 1 {
env.flagSet.Usage()
return fmt.Errorf("no command given")
}
cmd := f.Arg(0)
argsTail := f.Args()[1:]
cmd := env.flagSet.Arg(0)
env.args = env.flagSet.Args()[1:]
switch cmd {
case "help":
f.SetOutput(stdout)
f.Usage()
env.flagSet.SetOutput(env.stdout)
env.flagSet.Usage()
case "version":
cmdVersion()
default:
if configDir == "" {
configDir = getenv("GONF_CONFIG")
if configDir == "" {
f.Usage()
if env.configDir == "" {
env.configDir = env.getenv("GONF_CONFIG")
if env.configDir == "" {
env.flagSet.Usage()
return fmt.Errorf("the GONF_CONFIG environment variable is unset and the -config FLAG is missing. Please use one or the other")
}
}
switch cmd {
case "build":
return cmdBuild(ctx, f, argsTail, getenv, stdout, stderr)
return env.cmdBuild()
case "deploy":
return cmdDeploy(ctx, f, argsTail, getenv, stdout, stderr)
return env.cmdDeploy()
default:
f.Usage()
env.flagSet.Usage()
return fmt.Errorf("invalid command: %s", cmd)
}
}

View file

@ -18,22 +18,30 @@ import (
type sshClient struct {
agentConn net.Conn
client *ssh.Client
ctx context.Context
getenv func(string) string
stdout io.Writer
stderr io.Writer
}
func newSSHClient(context context.Context,
getenv func(string) string,
destination string,
) (*sshClient, error) {
var sshc sshClient
func (env *Env) newSSHClient(destination string) (*sshClient, error) {
sshc := sshClient{
agentConn: nil,
client: nil,
ctx: env.ctx,
getenv: env.getenv,
stdout: env.stdout,
stderr: env.stderr,
}
var err error
socket := getenv("SSH_AUTH_SOCK")
socket := sshc.getenv("SSH_AUTH_SOCK")
if sshc.agentConn, err = net.Dial("unix", socket); err != nil {
return nil, fmt.Errorf("failed to open SSH_AUTH_SOCK: %w", err)
}
agentClient := agent.NewClient(sshc.agentConn)
hostKeyCallback, err := knownhosts.New(filepath.Join(getenv("HOME"), ".ssh/known_hosts"))
hostKeyCallback, err := knownhosts.New(filepath.Join(sshc.getenv("HOME"), ".ssh/known_hosts"))
if err != nil {
return nil, fmt.Errorf("failed to create hostkeycallback function: %w", err)
}
@ -62,11 +70,7 @@ func (sshc *sshClient) Close() error {
return nil
}
func (sshc *sshClient) SendFile(
ctx context.Context,
stdout, stderr io.Writer,
filename string,
) error {
func (sshc *sshClient) SendFile(filename string) error {
session, err := sshc.client.NewSession()
if err != nil {
return fmt.Errorf("failed to create ssh client session: %w", err)
@ -93,8 +97,8 @@ func (sshc *sshClient) SendFile(
wg.Add(2)
errCh := make(chan error, 2)
session.Stdout = stdout
session.Stderr = stderr
session.Stdout = sshc.stdout
session.Stderr = sshc.stderr
if err := session.Start("scp -t /usr/local/bin/gonf-run"); err != nil {
return fmt.Errorf("failed to run scp: %w", err)
}
@ -124,7 +128,7 @@ func (sshc *sshClient) SendFile(
}
}()
ctx, cancel := context.WithTimeout(ctx, 60*time.Second)
ctx, cancel := context.WithTimeout(sshc.ctx, 60*time.Second)
defer cancel()
// wait for all waitgroup.Done() or the timeout