# HG changeset patch
# User Christophe de Vienne <christophe@cdevienne.info>
# Date 1730743151 -3600
#      Mon Nov 04 18:59:11 2024 +0100
# Node ID a13c368aa518f2e9ce540845e557565a94e0084f
# Parent  526787c227f5c915b2ede648cd182622fccc8c5c
cmd: add a 'generate-config' command

diff --git a/cmd/generate-config.go b/cmd/generate-config.go
new file mode 100644
--- /dev/null
+++ b/cmd/generate-config.go
@@ -0,0 +1,76 @@
+package cmd
+
+import (
+	"io"
+	"os"
+
+	"github.com/jessevdk/go-flags"
+)
+
+type GenerateConfigCmd struct {
+	Output string `short:"o" long:"config-output" no-ini:"t" description:"Destination of the generated configuration" default:"-"`
+
+	parser *flags.Parser
+}
+
+func (cmd *GenerateConfigCmd) Execute([]string) error {
+	var output io.Writer
+
+	var finalize func() error
+
+	if cmd.Output == "-" {
+		output = os.Stdout
+	} else {
+		f, err := os.CreateTemp("", "ioda-config.*.ini")
+		if err != nil {
+			return err
+		}
+
+		var finalized bool
+
+		defer func() {
+			if !finalized {
+				os.Remove(f.Name())
+			}
+		}()
+
+		finalize = func() error {
+			if err := os.Rename(f.Name(), cmd.Output); err != nil {
+				_ = os.Remove(f.Name())
+				return err
+			}
+			finalized = true
+
+			return nil
+		}
+
+		output = f
+	}
+
+	fp := flags.NewIniParser(cmd.parser)
+
+	fp.Write(output, flags.IniIncludeDefaults|flags.IniCommentDefaults|flags.IniDefault)
+
+	if _, err := output.Write([]byte{}); err != nil {
+		return err
+	}
+
+	if finalize != nil {
+		return finalize()
+	}
+
+	return nil
+}
+
+func SetupGenerateConfigCmd(program *Program) *GenerateConfigCmd {
+	cmd := GenerateConfigCmd{
+		parser: program.Parser,
+	}
+
+	_, err := program.Parser.AddCommand("generate-config", "Generate config", "", &cmd)
+	if err != nil {
+		program.Logger.Fatal().Msg(err.Error())
+	}
+
+	return &cmd
+}
diff --git a/cmd/program.go b/cmd/program.go
--- a/cmd/program.go
+++ b/cmd/program.go
@@ -50,8 +50,9 @@
 
 	DB *sqlx.DB
 
-	ServeCmd   *ServeCmd
-	MigrateCmd *MigrateCmd
+	ServeCmd          *ServeCmd
+	MigrateCmd        *MigrateCmd
+	GenerateConfigCmd *GenerateConfigCmd
 
 	hasDB           bool
 	dbMigrateSource source.Driver
@@ -145,6 +146,7 @@
 	}
 
 	program.ServeCmd = SetupServeCmd(&program)
+	program.GenerateConfigCmd = SetupGenerateConfigCmd(&program)
 
 	if program.hasDB {
 		g, err := parser.AddGroup("Database", "Database options", &program.DatabaseOptions)
diff --git a/cmd/serve.go b/cmd/serve.go
--- a/cmd/serve.go
+++ b/cmd/serve.go
@@ -9,7 +9,7 @@
 type ServeCmd struct {
 	Server *orusapi.Server
 
-	AutoMigrate bool `long:"auto-migrate" description:"Automaticaly migrate the database if necessary" env:"AUTOMIGRATE"`
+	AutoMigrate bool `long:"auto-migrate" ini-name:"auto-migrate" description:"Automaticaly migrate the database if necessary" env:"AUTOMIGRATE"`
 
 	program *Program
 }
diff --git a/cmd/xbus_serve.go b/cmd/xbus_serve.go
--- a/cmd/xbus_serve.go
+++ b/cmd/xbus_serve.go
@@ -25,7 +25,7 @@
 type XbusServeCmd struct {
 	program *Program
 
-	NoReconnect bool `long:"no-reconnect" description:"When the connection to xbus is lost, stops the program instead of attempting a reconnection"`
+	NoReconnect bool `long:"no-reconnect" ini-name:"no-reconnect" description:"When the connection to xbus is lost, stops the program instead of attempting a reconnection"`
 }
 
 func (cmd *XbusServeCmd) Execute([]string) error {
diff --git a/server.go b/server.go
--- a/server.go
+++ b/server.go
@@ -61,34 +61,34 @@
 
 // Server for the API.
 type Server struct {
-	EnabledListeners []string         `long:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec" env:"SCHEME"`
-	CleanupTimeout   time.Duration    `long:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"`
-	GracefulTimeout  time.Duration    `long:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"`
-	MaxHeaderSize    flagext.ByteSize `long:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"`
+	EnabledListeners []string         `long:"scheme" ini-name:"scheme" description:"the listeners to enable, this can be repeated and defaults to the schemes in the swagger spec" env:"SCHEME"`
+	CleanupTimeout   time.Duration    `long:"cleanup-timeout" ini-name:"cleanup-timeout" description:"grace period for which to wait before killing idle connections" default:"10s"`
+	GracefulTimeout  time.Duration    `long:"graceful-timeout" ini-name:"graceful-timeout" description:"grace period for which to wait before shutting down the server" default:"15s"`
+	MaxHeaderSize    flagext.ByteSize `long:"max-header-size" ini-name:"max-header-size" description:"controls the maximum number of bytes the server will read parsing the request header's keys and values, including the request line. It does not limit the size of the request body." default:"1MiB"`
 
-	SocketPath    flags.Filename `long:"socket-path" description:"the unix socket to listen on" default:"/var/run/server.sock"`
+	SocketPath    flags.Filename `long:"socket-path" ini-name:"socket-path" description:"the unix socket to listen on" default:"/var/run/server.sock"`
 	domainSocketL net.Listener
 
-	Host         string        `long:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
-	Port         int           `long:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
-	ListenLimit  int           `long:"listen-limit" description:"limit the number of outstanding requests"`
-	KeepAlive    time.Duration `long:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
-	ReadTimeout  time.Duration `long:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
-	WriteTimeout time.Duration `long:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
+	Host         string        `long:"host" ini-name:"host" description:"the IP to listen on" default:"localhost" env:"HOST"`
+	Port         int           `long:"port" ini-name:"port" description:"the port to listen on for insecure connections, defaults to a random value" env:"PORT"`
+	ListenLimit  int           `long:"listen-limit" ini-name:"listen-limit" description:"limit the number of outstanding requests"`
+	KeepAlive    time.Duration `long:"keep-alive" ini-name:"keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)" default:"3m"`
+	ReadTimeout  time.Duration `long:"read-timeout" ini-name:"read-timeout" description:"maximum duration before timing out read of the request" default:"30s"`
+	WriteTimeout time.Duration `long:"write-timeout" ini-name:"write-timeout" description:"maximum duration before timing out write of the response" default:"60s"`
 	httpServerL  net.Listener
 
-	TLSHost           string         `long:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
-	TLSPort           int            `long:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
-	TLSCertificate    flags.Filename `long:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
-	TLSCertificateKey flags.Filename `long:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
-	TLSCACertificate  flags.Filename `long:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
-	TLSListenLimit    int            `long:"tls-listen-limit" description:"limit the number of outstanding requests"`
-	TLSKeepAlive      time.Duration  `long:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`
-	TLSReadTimeout    time.Duration  `long:"tls-read-timeout" description:"maximum duration before timing out read of the request"`
-	TLSWriteTimeout   time.Duration  `long:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
+	TLSHost           string         `long:"tls-host" ini-name:"tls-host" description:"the IP to listen on for tls, when not specified it's the same as --host" env:"TLS_HOST"`
+	TLSPort           int            `long:"tls-port" ini-name:"tls-port" description:"the port to listen on for secure connections, defaults to a random value" env:"TLS_PORT"`
+	TLSCertificate    flags.Filename `long:"tls-certificate" ini-name:"tls-certificate" description:"the certificate to use for secure connections" env:"TLS_CERTIFICATE"`
+	TLSCertificateKey flags.Filename `long:"tls-key" ini-name:"tls-key" description:"the private key to use for secure connections" env:"TLS_PRIVATE_KEY"`
+	TLSCACertificate  flags.Filename `long:"tls-ca" ini-name:"tls-ca" description:"the certificate authority file to be used with mutual tls auth" env:"TLS_CA_CERTIFICATE"`
+	TLSListenLimit    int            `long:"tls-listen-limit" ini-name:"tls-listen-limit" description:"limit the number of outstanding requests"`
+	TLSKeepAlive      time.Duration  `long:"tls-keep-alive" ini-name:"tls-keep-alive" description:"sets the TCP keep-alive timeouts on accepted connections. It prunes dead TCP connections ( e.g. closing laptop mid-download)"`
+	TLSReadTimeout    time.Duration  `long:"tls-read-timeout" ini-name:"tls-write-timeout" description:"maximum duration before timing out read of the request"`
+	TLSWriteTimeout   time.Duration  `long:"tls-write-timeout" ini-name:"tls-write-timeout" description:"maximum duration before timing out write of the response"`
 	httpsServerL      net.Listener
 
-	Prometheus bool `long:"prometheus" description:"enable prometheus metrics on /metrics"`
+	Prometheus bool `long:"prometheus" ini-name:"prometheus" description:"enable prometheus metrics on /metrics"`
 
 	Environment string `no-flag:"t" description:"A environment name"`