From eb1982898cfb89e5ce74ba7b0c88ec10642d3730 Mon Sep 17 00:00:00 2001 From: Victor Lacasse-Beaudoin Date: Mon, 15 Jul 2024 16:52:04 -0400 Subject: [PATCH] rework: config and cmd Renamed `web` command to `server ui` (web is still an alias to ui) Completely changed the config options and flags Usage of PersistentFlags now allow clearer `--help` BREAKING: cmd modified BREAKING: config overhauled BREAKING: Bump API to v8 --- README.md | 21 +- client_test.go | 10 +- cmd.go | 74 ++--- config.go | 664 ++++++++++++++++++++++++++------------------ docker-compose.yaml | 27 +- go.mod | 2 +- request.go | 18 +- routes.go | 5 +- 8 files changed, 475 insertions(+), 346 deletions(-) diff --git a/README.md b/README.md index acda47f..822c4f1 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,18 @@ https://git.agecem.com/agecem/bottin Remplir .env avec les infos qui seront utilisées pour déployer le container -(Remplacer `bottin` par quelque chose de plus sécuritaire) +Au minimum, il faut ces 3 entrées: + +*Remplacer `bottin` par quelque chose de plus sécuritaire* ```sh -BOTTIN_API_KEY=bottin -BOTTIN_POSTGRES_DATABASE=bottin -BOTTIN_POSTGRES_PASSWORD=bottin -BOTTIN_POSTGRES_USER=bottin -BOTTIN_WEB_PASSWORD=bottin -BOTTIN_WEB_USER=bottin +BOTTIN_SERVER_DB_DATABASE=bottin +BOTTIN_SERVER_DB_PASSWORD=bottin +BOTTIN_SERVER_DB_USER=bottin ``` +*D'autres entrées peuvent être ajoutées, voir `config.go` pour les options* + Déployer avec docker-compose `$ docker-compose up -d` @@ -43,13 +44,13 @@ Pour modifier la configuration du serveur API `$ docker-compose exec -it api vi /etc/bottin/api.yaml` -*Y remplir au minimum le champs `api.key` (string)* +*Y remplir au minimum le champs `server.api.key` (string)* Pour modifier la configuration du client web -`$ docker-compose exec -it web vi /etc/bottin/web.yaml` +`$ docker-compose exec -it ui vi /etc/bottin/ui.yaml` -*Y remplir au minimum les champs `web.api.key` (string), `web.user` (string) et `web.password` (string)* +*Y remplir au minimum les champs `server.ui.api.key` (string), `server.ui.user` (string) et `server.ui.password` (string)* Redémarrer les containers une fois la configuration modifiée diff --git a/client_test.go b/client_test.go index 1593e4c..261eb20 100644 --- a/client_test.go +++ b/client_test.go @@ -34,15 +34,7 @@ func TestAPI(t *testing.T) { } defer httpClient.CloseIdleConnections() - var protocol string - switch cfg.API.TLS.Enabled { - case true: - protocol = "https" - case false: - protocol = "http" - } - - vokiClient := voki.New(&httpClient, "localhost", cfg.API.Key, cfg.API.Port, protocol) + vokiClient := voki.New(&httpClient, "localhost", cfg.Client.API.Key, cfg.Client.API.Port, cfg.Client.API.Protocol) apiClient := APIClient{vokiClient} t.Run("get API health", func(t *testing.T) { diff --git a/cmd.go b/cmd.go index 1518476..8fbd66b 100644 --- a/cmd.go +++ b/cmd.go @@ -32,6 +32,11 @@ func execute() { } } +var serverCmd = &cobra.Command{ + Use: "server", + Short: "Démarrer serveurs (API ou Web UI)", +} + // apiCmd represents the api command var apiCmd = &cobra.Command{ Use: "api", @@ -49,10 +54,12 @@ var apiCmd = &cobra.Command{ e.Pre(middleware.AddTrailingSlash()) - if cfg.API.Key != "" { + if cfg.Server.API.Key != "" { e.Use(middleware.KeyAuth(func(key string, c echo.Context) (bool, error) { - return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.API.Key)) == 1, nil + return subtle.ConstantTimeCompare([]byte(key), []byte(cfg.Server.API.Key)) == 1, nil })) + } else { + log.Println("Server started but no API key (server.api.key) was provided, using empty key (NOT RECOMMENDED FOR PRODUCTION)") } // DataClient @@ -63,12 +70,12 @@ var apiCmd = &cobra.Command{ ctx, fmt.Sprintf( "user=%s password=%s database=%s host=%s port=%d sslmode=%s ", - cfg.DB.User, - cfg.DB.Password, - cfg.DB.Database, - cfg.DB.Host, - cfg.DB.Port, - cfg.DB.SSLMode, + cfg.Server.API.DB.User, + cfg.Server.API.DB.Password, + cfg.Server.API.DB.Database, + cfg.Server.API.DB.Host, + cfg.Server.API.DB.Port, + cfg.Server.API.DB.SSLMode, )) if err != nil { log.Fatal("init pgx pool:", err) @@ -98,49 +105,50 @@ var apiCmd = &cobra.Command{ /* h := handlers.New(client) - e.GET("/v7/health/", h.GetHealth) + e.GET("/v8/health/", h.GetHealth) - e.POST("/v7/membres/", h.PostMembres) + e.POST("/v8/membres/", h.PostMembres) - e.GET("/v7/membres/", h.ListMembres) + e.GET("/v8/membres/", h.ListMembres) - e.GET("/v7/membres/:membre_id/", h.ReadMembre) + e.GET("/v8/membres/:membre_id/", h.ReadMembre) - e.PUT("/v7/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) + e.PUT("/v8/membres/:membre_id/prefered_name/", h.PutMembrePreferedName) - e.POST("/v7/programmes/", h.PostProgrammes) + e.POST("/v8/programmes/", h.PostProgrammes) - e.POST("/v7/seed/", h.PostSeed) + e.POST("/v8/seed/", h.PostSeed) */ // Execution - switch cfg.API.TLS.Enabled { + switch cfg.Server.API.TLS.Enabled { case false: e.Logger.Fatal( e.Start( - fmt.Sprintf(":%d", cfg.API.Port), + fmt.Sprintf(":%d", cfg.Server.API.Port), ), ) case true: //TODO - log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.API.TLS.Certfile, cfg.API.TLS.Keyfile) + log.Printf("dbg: certfile='%s' keyfile='%s'", cfg.Server.API.TLS.Certfile, cfg.Server.API.TLS.Keyfile) e.Logger.Fatal( e.StartTLS( - fmt.Sprintf(":%d", cfg.API.Port), - cfg.API.TLS.Certfile, - cfg.API.TLS.Keyfile, + fmt.Sprintf(":%d", cfg.Server.API.Port), + cfg.Server.API.TLS.Certfile, + cfg.Server.API.TLS.Keyfile, ), ) } }, } -// webCmd represents the web command -var webCmd = &cobra.Command{ - Use: "web", - Short: "Démarrer le client web", - Args: cobra.ExactArgs(0), +// uiCmd represents the ui command +var uiCmd = &cobra.Command{ + Use: "ui", + Aliases: []string{"web", "interface"}, + Short: "Démarrer l'interface Web UI", + Args: cobra.ExactArgs(0), Run: func(cmd *cobra.Command, args []string) { // Parse config var cfg Config @@ -157,8 +165,8 @@ var webCmd = &cobra.Command{ // Auth e.Use(middleware.BasicAuth(func(user, password string, c echo.Context) (bool, error) { - usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Web.User)) == 1 - passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Web.Password)) == 1 + usersMatch := subtle.ConstantTimeCompare([]byte(user), []byte(cfg.Server.UI.User)) == 1 + passwordsMatch := subtle.ConstantTimeCompare([]byte(password), []byte(cfg.Server.UI.Password)) == 1 return usersMatch && passwordsMatch, nil })) @@ -170,10 +178,10 @@ var webCmd = &cobra.Command{ // API Client apiClient := APIClient{voki.New( http.DefaultClient, - cfg.Web.API.Host, - cfg.Web.API.Key, - cfg.Web.API.Port, - cfg.Web.API.Protocol, + cfg.Server.UI.API.Host, + cfg.Server.UI.API.Key, + cfg.Server.UI.API.Port, + cfg.Server.UI.API.Protocol, )} defer apiClient.Voki.CloseIdleConnections() @@ -239,6 +247,6 @@ Programme: [%s] %s // Execution e.Logger.Fatal(e.Start( - fmt.Sprintf(":%d", cfg.Web.Port))) + fmt.Sprintf(":%d", cfg.Server.UI.Port))) }, } diff --git a/config.go b/config.go index c13b5a0..01eecd5 100644 --- a/config.go +++ b/config.go @@ -10,278 +10,52 @@ import ( "github.com/spf13/viper" ) -const ( - ViperAPITLSEnabled string = "api.tls.enabled" - FlagAPITLSEnabled string = "api-tls-enabled" - DefaultAPITLSEnabled bool = false - DescriptionAPITLSEnabled string = "Whether to use TLS or not. Requires certificate and private key files." - - ViperAPITLSCertfile string = "api.tls.certfile" - FlagAPITLSCertfile string = "api-tls-certfile" - DefaultAPITLSCertfile string = "/etc/bottin/cert.pem" - DescriptionAPITLSCertfile string = "Path to TLS certificate file" - - ViperAPITLSKeyfile string = "api.tls.keyfile" - FlagAPITLSKeyfile string = "api-tls-keyfile" - DefaultAPITLSKeyfile string = "/etc/bottin/key.pem" - DescriptionAPITLSKeyFile string = "Path to TLS private key file" - - ViperAPIPort string = "api.port" - FlagAPIPort string = "api-port" - DefaultAPIPort int = 1312 - DescriptionAPIPort string = "API server port" - - ViperAPIKey string = "api.key" - FlagAPIKey string = "api-key" - DefaultAPIKey string = "bottin" - DescriptionAPIKey string = "API server key. Leave empty for no key auth (not recommended)" - - ViperDBDatabase string = "db.database" - FlagDBDatabase string = "db-database" - DefaultDBDatabase string = "bottin" - DescriptionDBDatabase string = "Postgres database" - - ViperDBSSLMode string = "db.sslmode" - FlagDBSSLMode string = "db-sslmode" - DefaultDBSSLMode string = "prefer" - DescriptionDBSSLMode string = "Postgres sslmode" - - ViperDBHost string = "db.host" - FlagDBHost string = "db-host" - DefaultDBHost string = "db" - DescriptionDBHost string = "Postgres host" - - ViperDBPassword string = "db.password" - FlagDBPassword string = "db-password" - DefaultDBPassword string = "bottin" - DescriptionDBPassword string = "Postgres password" - - ViperDBPort string = "db.port" - FlagDBPort string = "db-port" - DefaultDBPort int = 5432 - DescriptionDBPort string = "Postgres port" - - ViperDBUser string = "db.user" - FlagDBUser string = "db-user" - DefaultDBUser string = "bottin" - DescriptionDBUser string = "Postgres user" - - ViperWebUser string = "web.user" - FlagWebUser string = "web-user" - DefaultWebUser string = "bottin" - DescriptionWebUser string = "Web client basic auth user" - - ViperWebPassword string = "web.password" - FlagWebPassword string = "web-password" - DefaultWebPassword string = "bottin" - DescriptionWebPassword string = "Web client basic auth password" - - ViperWebPort string = "web.port" - FlagWebPort string = "web-port" - DefaultWebPort int = 2312 - DescriptionWebPort string = "Web client port" - - ViperWebAPIHost string = "web.api.host" - FlagWebAPIHost string = "web-api-host" - DefaultWebAPIHost string = "api" - DescriptionWebAPIHost string = "Target API server host" - - ViperWebAPIKey string = "web.api.key" - FlagWebAPIKey string = "web-api-key" - DefaultWebAPIKey string = "bottin" - DescriptionWebAPIKey string = "Target API server key" - - ViperWebAPIPort string = "web.api.port" - FlagWebAPIPort string = "web-api-port" - DefaultWebAPIPort int = 1312 - DescriptionWebAPIPort string = "Target API server port" - - ViperWebAPIProtocol string = "web.api.protocol" - FlagWebAPIProtocol string = "web-api-protocol" - DefaultWebAPIProtocol string = "http" - DescriptionWebAPIProtocol string = "Target API server protocol (http/https)" -) - type Config struct { - API struct { - TLS struct { - Enabled bool `yaml:"enabled"` - - // Path to file containing TLS certificate - Certfile string `yaml:"certfile"` - - // Path to file containing TLS private key - Keyfile string `yaml:"keyfile"` - } `yaml:"tls"` - Port int `yaml:"port"` - Key string `yaml:"key"` - } `yaml:"api"` - DB struct { - Database string `yaml:"database"` - Host string `yaml:"host"` - SSLMode string `yaml:"sslmode"` - Password string `yaml:"password"` - Port int `yaml:"port"` - User string `yaml:"user"` - } `yaml:"db"` - Web struct { - User string `yaml:"user"` - Password string `yaml:"password"` - Port int `yaml:"port"` - API struct { + Client struct { + API struct { Host string `yaml:"host"` Key string `yaml:"key"` Port int `yaml:"port"` Protocol string `yaml:"protocol"` } `yaml:"api"` - } `yaml:"web"` -} + } `yaml:"client"` -// DefaultConfig returns a Config filled with the default values from the -// `Default*` constants defined in this file. -func DefaultConfig() (cfg Config) { - cfg.API.TLS.Enabled = DefaultAPITLSEnabled - cfg.API.TLS.Certfile = DefaultAPITLSCertfile - cfg.API.TLS.Keyfile = DefaultAPITLSKeyfile - cfg.API.Port = DefaultAPIPort - cfg.API.Key = DefaultAPIKey - cfg.DB.Database = DefaultDBDatabase - cfg.DB.Host = DefaultDBHost - cfg.DB.SSLMode = DefaultDBSSLMode - cfg.DB.Password = DefaultDBPassword - cfg.DB.Port = DefaultDBPort - cfg.DB.User = DefaultDBUser - cfg.Web.User = DefaultWebUser - cfg.Web.Password = DefaultWebPassword - cfg.Web.Port = DefaultWebPort - cfg.Web.API.Host = DefaultWebAPIHost - cfg.Web.API.Key = DefaultWebAPIKey - cfg.Web.API.Port = DefaultWebAPIPort - cfg.Web.API.Protocol = DefaultWebAPIProtocol - return -} - -func init() { - // rootCmd - - cobra.OnInitialize(initConfig) - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") - - // apiCmd - - rootCmd.AddCommand(apiCmd) - - // api.tls.enabled - apiCmd.Flags().Bool(FlagAPITLSEnabled, DefaultAPITLSEnabled, DescriptionAPITLSEnabled) - if err := viper.BindPFlag(ViperAPITLSEnabled, apiCmd.Flags().Lookup(FlagAPITLSEnabled)); err != nil { - log.Fatal(err) - } - - // api.tls.certfile - apiCmd.Flags().String(FlagAPITLSCertfile, DefaultAPITLSCertfile, DescriptionAPITLSCertfile) - if err := viper.BindPFlag(ViperAPITLSCertfile, apiCmd.Flags().Lookup(FlagAPITLSCertfile)); err != nil { - log.Fatal(err) - } - - // api.tls.keyfile - apiCmd.Flags().String(FlagAPITLSKeyfile, DefaultAPITLSKeyfile, DescriptionAPITLSKeyFile) - if err := viper.BindPFlag(ViperAPITLSKeyfile, apiCmd.Flags().Lookup(FlagAPITLSKeyfile)); err != nil { - log.Fatal(err) - } - - // api.key - apiCmd.Flags().String(FlagAPIKey, DefaultAPIKey, DescriptionAPIKey) - if err := viper.BindPFlag(ViperAPIKey, apiCmd.Flags().Lookup(FlagAPIKey)); err != nil { - log.Fatal(err) - } - - // api.port - apiCmd.Flags().Int(FlagAPIPort, DefaultAPIPort, DescriptionAPIPort) - if err := viper.BindPFlag(ViperAPIPort, apiCmd.Flags().Lookup(FlagAPIPort)); err != nil { - log.Fatal(err) - } - - // db.database - apiCmd.Flags().String(FlagDBDatabase, DefaultDBDatabase, DescriptionDBDatabase) - if err := viper.BindPFlag(ViperDBDatabase, apiCmd.Flags().Lookup(FlagDBDatabase)); err != nil { - log.Fatal(err) - } - - // db.sslmode - apiCmd.Flags().String(FlagDBSSLMode, DefaultDBSSLMode, DescriptionDBSSLMode) - if err := viper.BindPFlag(ViperDBSSLMode, apiCmd.Flags().Lookup(FlagDBSSLMode)); err != nil { - log.Fatal(err) - } - - // db.host - apiCmd.Flags().String(FlagDBHost, DefaultDBHost, DescriptionDBHost) - if err := viper.BindPFlag(ViperDBHost, apiCmd.Flags().Lookup(FlagDBHost)); err != nil { - log.Fatal(err) - } - - // db.password - apiCmd.Flags().String(FlagDBPassword, DefaultDBPassword, DescriptionDBPassword) - if err := viper.BindPFlag(ViperDBPassword, apiCmd.Flags().Lookup(FlagDBPassword)); err != nil { - log.Fatal(err) - } - - // db.port - apiCmd.Flags().Int(FlagDBPort, DefaultDBPort, DescriptionDBPort) - if err := viper.BindPFlag(ViperDBPort, apiCmd.Flags().Lookup(FlagDBPort)); err != nil { - log.Fatal(err) - } - - // db.user - apiCmd.Flags().String(FlagDBUser, DefaultDBUser, DescriptionDBUser) - if err := viper.BindPFlag(ViperDBUser, apiCmd.Flags().Lookup(FlagDBUser)); err != nil { - log.Fatal(err) - } - - // WebCmd - rootCmd.AddCommand(webCmd) - - // web.api.host - webCmd.Flags().String(FlagWebAPIHost, DefaultWebAPIHost, DescriptionWebAPIHost) - if err := viper.BindPFlag(ViperWebAPIHost, webCmd.Flags().Lookup(FlagWebAPIHost)); err != nil { - log.Fatal(err) - } - - // web.api.key - webCmd.Flags().String(FlagWebAPIKey, DefaultWebAPIKey, DescriptionWebAPIKey) - if err := viper.BindPFlag(ViperWebAPIKey, webCmd.Flags().Lookup(FlagWebAPIKey)); err != nil { - log.Fatal(err) - } - - // web.api.protocol - webCmd.Flags().String(FlagWebAPIProtocol, DefaultWebAPIProtocol, DescriptionWebAPIProtocol) - if err := viper.BindPFlag(ViperWebAPIProtocol, webCmd.Flags().Lookup(FlagWebAPIProtocol)); err != nil { - log.Fatal(err) - } - - // web.api.port - webCmd.Flags().Int(FlagWebAPIPort, DefaultWebAPIPort, DescriptionWebAPIPort) - if err := viper.BindPFlag(ViperWebAPIPort, webCmd.Flags().Lookup(FlagWebAPIPort)); err != nil { - log.Fatal(err) - } - - // web.password - webCmd.Flags().String(FlagWebPassword, DefaultWebPassword, DescriptionWebPassword) - if err := viper.BindPFlag(ViperWebPassword, webCmd.Flags().Lookup(FlagWebPassword)); err != nil { - log.Fatal(err) - } - - // web.port - webCmd.Flags().Int(FlagWebPort, DefaultWebPort, DescriptionWebPort) - if err := viper.BindPFlag(ViperWebPort, webCmd.Flags().Lookup(FlagWebPort)); err != nil { - log.Fatal(err) - } - - // web.user - webCmd.Flags().String(FlagWebUser, DefaultWebUser, DescriptionWebUser) - if err := viper.BindPFlag(ViperWebUser, webCmd.Flags().Lookup(FlagWebUser)); err != nil { - log.Fatal(err) - } + Server struct { + API struct { + DB struct { + Database string `yaml:"database"` + Host string `yaml:"host"` + Password string `yaml:"password"` + Port int `yaml:"port"` + SSLMode string `yaml:"sslmode"` + User string `yaml:"user"` + } `yaml:"db"` + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + TLS struct { + Enabled bool `yaml:"enabled"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` + } `yaml:"api"` + UI struct { + API struct { + Host string `yaml:"host"` + Key string `yaml:"key"` + Port int `yaml:"port"` + Protocol string `yaml:"protocol"` + } `yaml:"api"` + Password string `yaml:"password"` + Port int `yaml:"port"` + TLS struct { + Enabled bool `yaml:"enabled"` + Certfile string `yaml:"certfile"` + Keyfile string `yaml:"keyfile"` + } `yaml:"tls"` + User string `yaml:"user"` + } `yaml:"ui"` + } `yaml:"server"` } var cfgFile string @@ -311,3 +85,363 @@ func initConfig() { fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) } } + +func init() { + // rootCmd + + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.bottin.yaml)") + + // client.api.host + rootCmd.PersistentFlags().String( + "client-api-host", + "api", + "API server host", + ) + if err := viper.BindPFlag( + "client.api.host", + rootCmd.PersistentFlags().Lookup("client-api-host"), + ); err != nil { + log.Fatal(err) + } + + // client.api.key + rootCmd.PersistentFlags().String( + "client-api-key", + "bottin", + "API server key", + ) + if err := viper.BindPFlag( + "client.api.key", + rootCmd.PersistentFlags().Lookup("client-api-key"), + ); err != nil { + log.Fatal(err) + } + + // client.api.port + rootCmd.PersistentFlags().Int( + "client-api-port", + 1312, + "API server port", + ) + if err := viper.BindPFlag( + "client.api.port", + rootCmd.PersistentFlags().Lookup("client-api-port"), + ); err != nil { + log.Fatal(err) + } + + // client.api.protocol + rootCmd.PersistentFlags().String( + "client-api-protocol", + "https", + "API server protocol", + ) + if err := viper.BindPFlag( + "client.api.protocol", + rootCmd.PersistentFlags().Lookup("client-api-protocol"), + ); err != nil { + log.Fatal(err) + } + + // server + rootCmd.AddCommand(serverCmd) + + // server api + serverCmd.AddCommand(apiCmd) + + // server api db + // server.api.db.database + apiCmd.PersistentFlags().String( + "server-api-db-database", + "bottin", + "Postgres database name", + ) + if err := viper.BindPFlag( + "server.api.db.database", + apiCmd.PersistentFlags().Lookup("server-api-db-database"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.host + apiCmd.PersistentFlags().String( + "server-api-db-host", + "db", + "Postgres host name", + ) + if err := viper.BindPFlag( + "server.api.db.host", + apiCmd.PersistentFlags().Lookup("server-api-db-host"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.password + apiCmd.PersistentFlags().String( + "server-api-db-password", + "bottin", + "Postgres password", + ) + if err := viper.BindPFlag( + "server.api.db.password", + apiCmd.PersistentFlags().Lookup("server-api-db-password"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.port + apiCmd.PersistentFlags().Int( + "server-api-db-port", + 5432, + "Postgres port", + ) + if err := viper.BindPFlag( + "server.api.db.port", + apiCmd.PersistentFlags().Lookup("server-api-db-port"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.sslmode + apiCmd.PersistentFlags().String( + "server-api-db-sslmode", + "prefer", + "Postgres sslmode", + ) + if err := viper.BindPFlag( + "server.api.db.sslmode", + apiCmd.PersistentFlags().Lookup("server-api-db-sslmode"), + ); err != nil { + log.Fatal(err) + } + + // server.api.db.user + apiCmd.PersistentFlags().String( + "server-api-db-user", + "bottin", + "Postgres user name", + ) + if err := viper.BindPFlag( + "server.api.db.user", + apiCmd.PersistentFlags().Lookup("server-api-db-user"), + ); err != nil { + log.Fatal(err) + } + + // server.api.host + apiCmd.PersistentFlags().String( + "server-api-host", + "", + "API server hostname or IP to answer on (empty = any)", + ) + if err := viper.BindPFlag( + "server.api.host", + apiCmd.PersistentFlags().Lookup("server-api-host"), + ); err != nil { + log.Fatal(err) + } + + // server.api.key + apiCmd.PersistentFlags().String( + "server-api-key", + "bottin", + "API server key", + ) + if err := viper.BindPFlag( + "server.api.key", + apiCmd.PersistentFlags().Lookup("server-api-key"), + ); err != nil { + log.Fatal(err) + } + + // server.api.port + apiCmd.PersistentFlags().Int( + "server-api-port", + 1312, + "API server port", + ) + if err := viper.BindPFlag( + "server.api.port", + apiCmd.PersistentFlags().Lookup("server-api-port"), + ); err != nil { + log.Fatal(err) + } + + // server api tls + // server.api.tls.enabled + apiCmd.PersistentFlags().Bool( + "server-api-tls-enabled", + true, + "Use TLS for API server connections (requires certfile and keyfile)", + ) + if err := viper.BindPFlag( + "server.api.tls.enabled", + apiCmd.PersistentFlags().Lookup("server-api-tls-enabled"), + ); err != nil { + log.Fatal(err) + } + + // server.api.tls.certfile + apiCmd.PersistentFlags().String( + "server-api-tls-certfile", + "/etc/bottin/cert.pem", + "Path to certificate file", + ) + if err := viper.BindPFlag( + "server.api.tls.certfile", + apiCmd.PersistentFlags().Lookup("server-api-tls-certfile"), + ); err != nil { + log.Fatal(err) + } + + // server.api.tls.keyfile + apiCmd.PersistentFlags().String( + "server-api-tls-keyfile", + "/etc/bottin/key.pem", + "Path to private key file", + ) + if err := viper.BindPFlag( + "server.api.tls.keyfile", + apiCmd.PersistentFlags().Lookup("server-api-tls-keyfile"), + ); err != nil { + log.Fatal(err) + } + + // server ui + serverCmd.AddCommand(uiCmd) + + // server ui api + + // server.ui.api.host + uiCmd.PersistentFlags().String( + "server-ui-api-host", + "api", + "Web UI backend API server host name", + ) + if err := viper.BindPFlag( + "server.ui.api.host", + uiCmd.PersistentFlags().Lookup("server-ui-api-host"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.api.key + uiCmd.PersistentFlags().String( + "server-ui-api-key", + "bottin", + "Web UI backend API server key", + ) + if err := viper.BindPFlag( + "server.ui.api.key", + uiCmd.PersistentFlags().Lookup("server-ui-api-key"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.api.port + uiCmd.PersistentFlags().Int( + "server-ui-api-port", + 1312, + "Web UI backend API server port", + ) + if err := viper.BindPFlag( + "server.ui.api.port", + uiCmd.PersistentFlags().Lookup("server-ui-api-port"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.api.protocol + uiCmd.PersistentFlags().String( + "server-ui-api-protocol", + "https", + "Web UI backend API server protocol", + ) + if err := viper.BindPFlag( + "server.ui.api.protocol", + uiCmd.PersistentFlags().Lookup("server-ui-api-protocol"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.password + uiCmd.PersistentFlags().String( + "server-ui-password", + "bottin", + "Web UI password", + ) + if err := viper.BindPFlag( + "server.ui.password", + uiCmd.PersistentFlags().Lookup("server-ui-password"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.port + uiCmd.PersistentFlags().Int( + "server-ui-port", + 2312, + "Web UI port", + ) + if err := viper.BindPFlag( + "server.ui.port", + uiCmd.PersistentFlags().Lookup("server-ui-port"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.user + uiCmd.PersistentFlags().String( + "server-ui-user", + "bottin", + "Web UI user", + ) + if err := viper.BindPFlag( + "server.ui.user", + uiCmd.PersistentFlags().Lookup("server-ui-user"), + ); err != nil { + log.Fatal(err) + } + + // server ui tls + // server.ui.tls.enabled + uiCmd.PersistentFlags().Bool( + "server-ui-tls-enabled", + true, + "Web UI enable TLS (requires certfile and keyfile)", + ) + if err := viper.BindPFlag( + "server.ui.tls.enabled", + uiCmd.PersistentFlags().Lookup("server-ui-tls-enabled"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.tls.certfile + uiCmd.PersistentFlags().String( + "server-ui-tls-certfile", + "/etc/bottin/cert.pem", + "Path to Web UI TLS certificate file", + ) + if err := viper.BindPFlag( + "server.ui.tls.certfile", + uiCmd.PersistentFlags().Lookup("server-ui-tls-certfile"), + ); err != nil { + log.Fatal(err) + } + + // server.ui.tls.keyfile + uiCmd.PersistentFlags().String( + "server-ui-tls-keyfile", + "/etc/bottin/key.pem", + "Path to Web UI TLS private key file", + ) + if err := viper.BindPFlag( + "server.ui.tls.keyfile", + uiCmd.PersistentFlags().Lookup("server-ui-tls-keyfile"), + ); err != nil { + log.Fatal(err) + } +} diff --git a/docker-compose.yaml b/docker-compose.yaml index 9f2e604..1156be7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -3,9 +3,9 @@ services: db: image: 'docker.io/library/postgres:16' environment: - POSTGRES_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" - POSTGRES_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" - POSTGRES_USER: "${BOTTIN_POSTGRES_USER}" + POSTGRES_DATABASE: "${BOTTIN_SERVER_API_DB_DATABASE}" + POSTGRES_PASSWORD: "${BOTTIN_SERVER_API_DB_PASSWORD}" + POSTGRES_USER: "${BOTTIN_SERVER_API_DB_USER}" volumes: - 'db-data:/var/lib/postgresql/data' restart: 'unless-stopped' @@ -15,33 +15,26 @@ services: - db build: . image: 'git.agecem.com/agecem/bottin:latest' - environment: - BOTTIN_DB_DATABASE: "${BOTTIN_POSTGRES_DATABASE}" - BOTTIN_DB_PASSWORD: "${BOTTIN_POSTGRES_PASSWORD}" - BOTTIN_DB_USER: "${BOTTIN_POSTGRES_USER}" - BOTTIN_API_KEY: "${BOTTIN_API_KEY}" + env_file: '.env' ports: - '1312:1312' volumes: - 'api-config:/etc/bottin' restart: 'unless-stopped' - command: ['bottin', '--config', '/etc/bottin/api.yaml', 'api'] + command: ['bottin', '--config', '/etc/bottin/api.yaml', 'server', 'api'] - web: + ui: depends_on: - api build: . image: 'git.agecem.com/agecem/bottin:latest' - environment: - BOTTIN_WEB_API_KEY: "${BOTTIN_API_KEY}" - BOTTIN_WEB_PASSWORD: "${BOTTIN_WEB_PASSWORD}" - BOTTIN_WEB_USER: "${BOTTIN_WEB_USER}" + env_file: '.env' ports: - '2312:2312' volumes: - - 'web-config:/etc/bottin' + - 'ui-config:/etc/bottin' restart: 'unless-stopped' - command: ['bottin', '--config', '/etc/bottin/web.yaml', 'web'] + command: ['bottin', '--config', '/etc/bottin/ui.yaml', 'server', 'ui'] # adminer: # image: adminer @@ -54,4 +47,4 @@ services: volumes: db-data: api-config: - web-config: + ui-config: diff --git a/go.mod b/go.mod index 2c52fee..489b37d 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module git.agecem.com/agecem/bottin/v7 +module git.agecem.com/agecem/bottin/v8 go 1.22.0 diff --git a/request.go b/request.go index 8f47f10..f1a73b4 100644 --- a/request.go +++ b/request.go @@ -23,7 +23,7 @@ func (request HealthGETRequest) Request(v *voki.Voki) (response HealthGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - "/api/v7/health/", + "/api/v8/health/", nil, true, ) @@ -64,7 +64,7 @@ func (request ProgrammesPOSTRequest) Request(v *voki.Voki) (response ProgrammesP statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/v7/programme/", + "/api/v8/programme/", &buf, true, ) @@ -105,7 +105,7 @@ func (request MembresPOSTRequest) Request(v *voki.Voki) (response MembresPOSTRes statusCode, body, err := v.CallAndParse( http.MethodPost, - "/api/v7/membre/", + "/api/v8/membre/", &buf, true, ) @@ -146,7 +146,7 @@ func (request MembreGETRequest) Request(v *voki.Voki) (response MembreGETRespons statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/%s/", request.Param.MembreID), + fmt.Sprintf("/api/v8/membre/%s/", request.Param.MembreID), nil, true, ) @@ -180,7 +180,7 @@ func (request MembresGETRequest) Request(v *voki.Voki) (response MembresGETRespo statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v8/membre/?limit=%d", request.Query.Limit), nil, true, ) @@ -224,7 +224,7 @@ func (request MembrePreferedNamePUTRequest) Request(v *voki.Voki) (response Memb statusCode, body, err := v.CallAndParse( http.MethodPut, - fmt.Sprintf("/api/v7/membre/%s/prefered_name/", request.Param.MembreID), + fmt.Sprintf("/api/v8/membre/%s/prefered_name/", request.Param.MembreID), &buf, true, ) @@ -258,7 +258,7 @@ func (request ProgrammesGETRequest) Request(v *voki.Voki) (response ProgrammesGE statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/programme/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v8/programme/?limit=%d", request.Query.Limit), nil, true, ) @@ -292,7 +292,7 @@ func (request MembresDisplayGETRequest) Request(v *voki.Voki) (response MembresD statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/display/?limit=%d", request.Query.Limit), + fmt.Sprintf("/api/v8/membre/display/?limit=%d", request.Query.Limit), nil, true, ) @@ -333,7 +333,7 @@ func (request MembreDisplayGETRequest) Request(v *voki.Voki) (response MembreDis statusCode, body, err := v.CallAndParse( http.MethodGet, - fmt.Sprintf("/api/v7/membre/%s/display/", request.Param.MembreID), + fmt.Sprintf("/api/v8/membre/%s/display/", request.Param.MembreID), nil, true, ) diff --git a/routes.go b/routes.go index ca7dcb2..a631aba 100644 --- a/routes.go +++ b/routes.go @@ -17,7 +17,7 @@ func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { _ = db _ = cfg - apiPath := "/api/v7" + apiPath := "/api/v8" apiGroup := e.Group(apiPath) p := pave.New() if err := pave.EchoRegister[HealthGETRequest]( @@ -248,7 +248,8 @@ func addRoutes(e *echo.Echo, db *PostgresClient, cfg Config) error { } } else { - //TODO cfg.API.DefaultLimit + //TODO cfg.Server.API.DefaultLimit + //TODO cfg.Client.API.Limit request.Query.Limit = 1000 }