goshort/cmd/main.go
2023-08-26 11:47:46 -03:00

101 lines
2.6 KiB
Go

//nolint:forbidigo // This is part of the CLI and the errors will be written as fmt.Pxxx
package cmd
import (
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
devcmd "git.maronato.dev/maronato/goshort/cmd/dev"
healthcheckcmd "git.maronato.dev/maronato/goshort/cmd/healthcheck"
servecmd "git.maronato.dev/maronato/goshort/cmd/serve"
"git.maronato.dev/maronato/goshort/internal/config"
"git.maronato.dev/maronato/goshort/internal/util/logging"
"github.com/peterbourgon/ff/v3/ffcli"
)
func Run() {
// Create the application-wide context, and
// implement graceful shutdown.
ctx, cancel := context.WithCancel(context.Background())
trapSignalsCrossPlatform(cancel)
// Create the root command and register subcommands.
rootCmd, cfg := newRootCmd()
rootCmd.Subcommands = []*ffcli.Command{
servecmd.New(cfg),
healthcheckcmd.New(cfg),
}
// Look for the env ENV_DOCKER=true to disable the dev command
// since the docker image won't have node installed.
if os.Getenv("ENV_DOCKER") != "true" {
rootCmd.Subcommands = append(rootCmd.Subcommands, devcmd.New(cfg))
}
// Parse the command-line arguments.
if err := rootCmd.Parse(os.Args[1:]); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
// Validate config
if err := config.Validate(cfg); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
// Create system logger
l := logging.NewLogger(cfg)
ctx = logging.WithLogger(ctx, l)
// Run the command.
if err := rootCmd.Run(ctx); err != nil {
fmt.Fprintf(os.Stderr, "%v\n", err)
os.Exit(1)
}
}
// newRootCmd constructs a root command from the provided options.
func newRootCmd() (*ffcli.Command, *config.Config) {
var cfg config.Config
fs := flag.NewFlagSet("goshort", flag.ContinueOnError)
return &ffcli.Command{
Name: "goshort",
ShortUsage: "goshort <subcommand> [flags]",
ShortHelp: "goshort is a tiny URL shortener",
FlagSet: fs,
Exec: func(ctx context.Context, args []string) error {
return flag.ErrHelp
},
}, &cfg
}
// https://github.com/caddyserver/caddy/blob/fbb0ecfa322aa7710a3448453fd3ae40f037b8d1/sigtrap.go#L37
// trapSignalsCrossPlatform captures SIGINT or interrupt (depending
// on the OS), which initiates a graceful shutdown. A second SIGINT
// or interrupt will forcefully exit the process immediately.
func trapSignalsCrossPlatform(cancel context.CancelFunc) {
go func() {
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGINT)
for i := 0; true; i++ {
<-shutdown
if i > 0 {
fmt.Printf("\nForce quit\n")
os.Exit(1)
}
fmt.Printf("\nGracefully shutting down. Press Ctrl+C again to force quit\n")
cancel()
}
}()
}