This commit is contained in:
Victor Lacasse-Beaudoin 2024-12-30 17:30:37 -05:00
parent cc8c8ef967
commit cdb16a9b60
9 changed files with 212 additions and 50 deletions

51
cmd.go Normal file
View file

@ -0,0 +1,51 @@
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/spf13/cobra"
)
func init() {
// rootCmd
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.bottin.agendas.yaml)")
if err := BindFlags(rootCmd.PersistentFlags()); err != nil {
log.Fatal(err)
}
}
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "agendas",
//Args: cobra.(0),
Short: "Gestion de distribution d'agendas",
Run: func(c *cobra.Command, args []string) {
log.Println("Starting bottin agendas microservice")
ctx := context.TODO()
cfg, err := ParseConfig()
if err != nil {
log.Fatal(err)
}
if len(args) > 0 {
if args[0] == "debugConfig" {
log.Println("debug: Printing current config")
cfgAsBytes, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
log.Fatal("Cannot marshal config for printing:", err)
}
fmt.Println(string(cfgAsBytes))
}
}
if err := run(ctx, cfg); err != nil {
log.Fatal(err)
}
},
}

View file

@ -1,7 +1,81 @@
package main package main
import "git.agecem.com/bottin/bottin/v10/pkg/bottin" import (
"fmt"
"os"
"strings"
"git.agecem.com/bottin/bottin/v10/pkg/bottin"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
type Config struct { type Config struct {
// Bottin holds a client config to contact a bottin API server
// Cannot be set using environment variables
Bottin bottin.APIClientConfig Bottin bottin.APIClientConfig
// TLS holds options for TLS / SSL / HTTPS
TLS struct {
// Cert holds the public certificate (or path to a file containing one) for user interface TLS
Cert string
// Key holds the private key (or path to a file containing one) for user interface TLS
Key string
// Enabled holds the TLS activation state
Enabled bool
}
// Credentials holds username-password pairs for basic authentication
Credentials map[string]string
// Port holds the port on which to expose the user interface
Port int
}
func ParseConfig() (cfg Config, err error) {
return cfg, viper.Unmarshal(&cfg)
}
var cfgFile string
// initConfig reads in config file and ENV variables if set.
func initConfig() {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Find home directory.
home, err := os.UserHomeDir()
cobra.CheckErr(err)
// Search config in home directory with name ".bottin.agendas" (without extension).
viper.AddConfigPath(home)
viper.SetConfigType("yaml")
viper.SetConfigName(".bottin.agendas")
}
// Read in env, will find matching viper bindings in flag.go [BindFlags]
viper.SetEnvPrefix("AGENDAS")
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
viper.AutomaticEnv() // read in environment variables that match
// If a config file is found, read it in.
if err := viper.ReadInConfig(); err == nil {
fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
}
}
func init() {
cobra.OnInitialize(initConfig)
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
} }

51
flag.go Normal file
View file

@ -0,0 +1,51 @@
package main
import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const (
configPort = "Port"
defaultPort = 3333
flagPort = "port"
configTLSEnabled = "TLS.Enabled"
defaultTLSEnabled = true
flagTLSEnabled = "tls-enabled"
configTLSCert = "TLS.Cert"
defaultTLSCert = ""
flagTLSCert = "tls-cert"
configTLSKey = "TLS.Key"
defaultTLSKey = ""
flagTLSKey = "tls-key"
)
// BindClientFlags declares client-related flags and config options in the specified *pflag.FlagSet
func BindFlags(set *pflag.FlagSet) error {
// Credentials -> seulement par config
set.Int(flagPort, defaultPort, "User interface port")
if err := viper.BindPFlag(configPort, set.Lookup(flagPort)); err != nil {
return err
}
set.Bool(flagTLSEnabled, defaultTLSEnabled, "User interface TLS state")
if err := viper.BindPFlag(configTLSEnabled, set.Lookup(flagTLSEnabled)); err != nil {
return err
}
set.String(flagTLSKey, defaultTLSKey, "User interface TLS private key (or path to file)")
if err := viper.BindPFlag(configTLSKey, set.Lookup(flagTLSKey)); err != nil {
return err
}
set.String(flagTLSCert, defaultTLSCert, "User interface TLS certificate (or path to file)")
if err := viper.BindPFlag(configTLSCert, set.Lookup(flagTLSCert)); err != nil {
return err
}
return nil
}

7
go.mod
View file

@ -6,13 +6,16 @@ require (
codeberg.org/vlbeaudoin/voki/v3 v3.0.1 codeberg.org/vlbeaudoin/voki/v3 v3.0.1
git.agecem.com/bottin/bottin/v10 v10.4.1 git.agecem.com/bottin/bottin/v10 v10.4.1
github.com/labstack/echo/v4 v4.13.3 github.com/labstack/echo/v4 v4.13.3
golang.org/x/term v0.27.0 github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.19.0
) )
require ( require (
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 // indirect github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.1 // indirect github.com/jackc/pgx/v5 v5.7.1 // indirect
@ -28,8 +31,6 @@ require (
github.com/sourcegraph/conc v0.3.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect

8
go.sum
View file

@ -2,6 +2,7 @@ codeberg.org/vlbeaudoin/voki/v3 v3.0.1 h1:pFjd/ZKsu4eOzRJYViE9F1S3RglSkAuIiqCo9I
codeberg.org/vlbeaudoin/voki/v3 v3.0.1/go.mod h1:+6LMXosAu2ijNKV04sMwkeujpH+cghZU1fydqj2y95g= codeberg.org/vlbeaudoin/voki/v3 v3.0.1/go.mod h1:+6LMXosAu2ijNKV04sMwkeujpH+cghZU1fydqj2y95g=
git.agecem.com/bottin/bottin/v10 v10.4.1 h1:mRZfqnLhGN9Qb+iQvfOvZM5pxXqSlkiXqIMX59zPAS8= git.agecem.com/bottin/bottin/v10 v10.4.1 h1:mRZfqnLhGN9Qb+iQvfOvZM5pxXqSlkiXqIMX59zPAS8=
git.agecem.com/bottin/bottin/v10 v10.4.1/go.mod h1:KTwlqY5XdVi9F7cpwy3hxYN1DQm+74tBv7Wc9rfKXuM= git.agecem.com/bottin/bottin/v10 v10.4.1/go.mod h1:KTwlqY5XdVi9F7cpwy3hxYN1DQm+74tBv7Wc9rfKXuM=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -16,6 +17,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
@ -48,6 +51,7 @@ github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRI
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
@ -58,6 +62,8 @@ github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
@ -96,8 +102,6 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=

44
main.go
View file

@ -2,38 +2,16 @@ package main
import ( import (
"context" "context"
"fmt"
"log"
"net/http" "net/http"
"codeberg.org/vlbeaudoin/voki/v3" "codeberg.org/vlbeaudoin/voki/v3"
"git.agecem.com/bottin/bottin/v10/pkg/bottin" "git.agecem.com/bottin/bottin/v10/pkg/bottin"
"golang.org/x/term"
) )
// Entry // Entry
func init() {}
func main() { func main() {
cfg := Config{} // Start commandline parsing then call `run`
cfg.Bottin.Host = "api.bottin.agecem.com" Execute()
cfg.Bottin.Port = 443
cfg.Bottin.TLS.Enabled = true
ctx := context.TODO()
fmt.Println("bottin password (no echo): ")
password, err := term.ReadPassword(0)
if err != nil {
log.Fatal(err)
}
cfg.Bottin.Key = string(password)
if err := run(ctx, cfg); err != nil {
log.Fatal(err)
}
} }
func run(ctx context.Context, cfg Config) error { func run(ctx context.Context, cfg Config) error {
@ -41,23 +19,21 @@ func run(ctx context.Context, cfg Config) error {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
default: default:
var protocol string
if cfg.Bottin.TLS.Enabled {
protocol = "https"
} else {
protocol = "http"
}
bottinClient := bottin.APIClient{Caller: voki.New( bottinClient := bottin.APIClient{Caller: voki.New(
http.DefaultClient, http.DefaultClient,
cfg.Bottin.Host, cfg.Bottin.Host,
cfg.Bottin.Key, cfg.Bottin.Key,
cfg.Bottin.Port, cfg.Bottin.Port,
protocol, func() string {
if cfg.Bottin.TLS.Enabled {
return "https"
} else {
return "http"
}
}(),
)} )}
if err := RunServer(ctx, &bottinClient); err != nil && err != http.ErrServerClosed { if err := RunServer(ctx, cfg, &bottinClient); err != nil && err != http.ErrServerClosed {
return err return err
} }

View file

@ -4,12 +4,13 @@ import (
"context" "context"
"fmt" "fmt"
"git.agecem.com/bottin/agendas/ui"
"git.agecem.com/bottin/bottin/v10/pkg/bottin" "git.agecem.com/bottin/bottin/v10/pkg/bottin"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware" "github.com/labstack/echo/v4/middleware"
) )
func RunServer(ctx context.Context, bottinClient *bottin.APIClient) error { func RunServer(ctx context.Context, cfg Config, bottinClient *bottin.APIClient) error {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return ctx.Err() return ctx.Err()
@ -20,14 +21,23 @@ func RunServer(ctx context.Context, bottinClient *bottin.APIClient) error {
e := echo.New() e := echo.New()
e.Renderer = NewRenderer() e.Renderer = ui.NewRenderer()
e.Pre(middleware.AddTrailingSlash()) e.Pre(middleware.AddTrailingSlash())
//TODO basic auth
//TODO log successful basic auths username
e.GET("/", UIIndex(ctx, bottinClient)) e.GET("/", UIIndex(ctx, bottinClient))
e.GET("/membre/", UIReadMembre(ctx, bottinClient)) e.GET("/membre/", UIReadMembre(ctx, bottinClient))
return e.Start(":3333") address := fmt.Sprintf(":%d", cfg.Port)
if cfg.TLS.Enabled {
return e.StartTLS(address, cfg.TLS.Cert, cfg.TLS.Key)
} else {
return e.Start(address)
}
} }
} }

View file

@ -4,7 +4,3 @@ import "embed"
//go:embed *.html //go:embed *.html
var htmlFS embed.FS var htmlFS embed.FS
func HTMLFS() embed.FS {
return htmlFS
}

View file

@ -1,10 +1,9 @@
package main package ui
import ( import (
"io" "io"
"text/template" "text/template"
"git.agecem.com/bottin/agendas/ui"
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
@ -18,6 +17,6 @@ func (t *Renderer) Render(w io.Writer, name string, data any, c echo.Context) er
func NewRenderer() *Renderer { func NewRenderer() *Renderer {
return &Renderer{ return &Renderer{
templates: template.Must(template.ParseFS(ui.HTMLFS(), "*html")), templates: template.Must(template.ParseFS(htmlFS, "*html")),
} }
} }