Move to koanf and urfavecli #50

Merged
amigan merged 6 commits from urfavecli into trunk 2024-11-24 16:04:07 -05:00
14 changed files with 352 additions and 122 deletions

View file

@ -13,28 +13,52 @@ import (
"dynatron.me/x/stillbox/pkg/cmd/serve"
"dynatron.me/x/stillbox/pkg/config"
"github.com/spf13/cobra"
"github.com/urfave/cli/v2"
)
const DefaultConfig = "config.yaml"
func main() {
configFile := DefaultConfig
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: common.TimeFormat})
rootCmd := &cobra.Command{
Use: common.AppName,
}
rootCmd.PersistentFlags().BoolP("version", "V", false, "show version")
cfg := config.New(rootCmd)
rootCmd.PreRun = func(cmd *cobra.Command, args []string) {
v, _ := rootCmd.PersistentFlags().GetBool("version")
if v {
fmt.Print(version.String())
os.Exit(0)
}
cfg := config.New(&configFile)
app := &cli.App{
Name: common.AppName,
Usage: "a scanner call server",
UseShortOptionHandling: true,
Before: cfg.Before,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",
Value: DefaultConfig,
Usage: "configuration file",
Destination: &configFile,
Aliases: []string{"c"},
},
&cli.BoolFlag{
Name: "version",
Aliases: []string{"V"},
Action: func(_ *cli.Context, v bool) error {
if v {
fmt.Print(version.String())
os.Exit(0)
}
return nil
},
DisableDefaultText: true,
},
},
Commands: []*cli.Command{
serve.Command(cfg),
admin.Command(cfg),
},
}
cmds := append([]*cobra.Command{serve.Command(cfg)}, admin.Command(cfg)...)
rootCmd.AddCommand(cmds...)
// cobra is already checking for errors and will print them
_ = rootCmd.Execute()
err := app.Run(os.Args)
if err != nil {
os.Stderr.Write([]byte("Error: " + err.Error() + "\n"))
os.Exit(1)
}
}

15
go.mod
View file

@ -17,11 +17,15 @@ require (
github.com/gorilla/websocket v1.5.3
github.com/hajimehoshi/oto v1.0.1
github.com/jackc/pgx/v5 v5.7.1
github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/env v1.0.0
github.com/knadh/koanf/providers/file v1.1.2
github.com/knadh/koanf/v2 v2.1.2
github.com/nikoksr/notify v1.1.0
github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.8.1
github.com/stretchr/testify v1.9.0
github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300
github.com/urfave/cli/v2 v2.27.5
golang.org/x/crypto v0.29.0
golang.org/x/sync v0.9.0
golang.org/x/term v0.26.0
@ -32,18 +36,20 @@ require (
require (
github.com/ajg/form v1.5.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-audio/audio v1.0.0 // indirect
github.com/go-audio/riff v1.0.0 // indirect
github.com/goccy/go-json v0.10.3 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
github.com/lestrrat-go/httpcc v1.0.1 // indirect
github.com/lestrrat-go/httprc v1.0.6 // indirect
@ -52,10 +58,13 @@ require (
github.com/lestrrat-go/option v1.0.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/segmentio/asm v1.2.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/exp/shiny v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/image v0.22.0 // indirect

30
go.sum
View file

@ -11,7 +11,8 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoU
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -29,6 +30,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4=
github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
@ -71,8 +74,6 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
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/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
@ -85,6 +86,16 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA=
github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/knadh/koanf/parsers/yaml v0.1.0 h1:ZZ8/iGfRLvKSaMEECEBPM1HQslrZADk8fP1XFUxVI5w=
github.com/knadh/koanf/parsers/yaml v0.1.0/go.mod h1:cvbUDC7AL23pImuQP0oRw/hPuccrNBS2bps8asS0CwY=
github.com/knadh/koanf/providers/env v1.0.0 h1:ufePaI9BnWH+ajuxGGiJ8pdTG0uLEUWC7/HDDPGLah0=
github.com/knadh/koanf/providers/env v1.0.0/go.mod h1:mzFyRZueYhb37oPmC1HAv/oGEEuyvJDA98r3XAa8Gak=
github.com/knadh/koanf/providers/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
github.com/knadh/koanf/v2 v2.1.2 h1:I2rtLRqXRy1p01m/utEtpZSSA6dcJbgGVuE27kW2PzQ=
github.com/knadh/koanf/v2 v2.1.2/go.mod h1:Gphfaen0q1Fc1HTgJgSTC4oRX9R2R5ErYMZJy8fLJBo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@ -110,6 +121,10 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
@ -132,14 +147,11 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
@ -151,6 +163,10 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300 h1:XQdibLKagjdevRB6vAjVY4qbSr8rQ610YzTkWcxzxSI=
github.com/tcolgate/mp3 v0.0.0-20170426193717-e79c5a46d300/go.mod h1:FNa/dfN95vAYCNFrIKRrlRo+MBLbwmR9Asa5f2ljmBI=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 h1:UP6IpuHFkUgOQL9FFQFrZ+5LiwhhYRbi7VZSIx6Nj5s=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0/go.mod h1:qxuZLtbq5QDtdeSHsS7bcf6EH6uO6jUAgk764zd3rhM=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=

View file

@ -1,34 +1,31 @@
package common
import (
"github.com/spf13/cobra"
"github.com/urfave/cli/v2"
)
const AppName = "stillbox"
const (
AppName = "stillbox"
EnvPrefix = "STILLBOX_"
)
const (
TimeFormat = "Jan 2 15:04:05"
)
type cmdOptions interface {
Options(*cobra.Command, []string) error
Options(*cli.Context) error
Execute() error
}
func RunE(c cmdOptions) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) error {
err := c.Options(cmd, args)
func Action(c cmdOptions) cli.ActionFunc {
return func(ctx *cli.Context) error {
err := c.Options(ctx)
if err != nil {
cmd.SilenceUsage = true
return err
}
err = c.Execute()
if err != nil {
cmd.SilenceUsage = true
}
return err
return c.Execute()
}
}

View file

@ -2,7 +2,7 @@ POST /api/talkgroup/ HTTP/1.1
Host: xenon:3051
User-Agent: curl/8.10.1
Accept: */*
Content-Length: 16
Content-Length: 27
Content-Type: application/x-www-form-urlencoded
page=1&perPage=2&orderBy=id

View file

@ -68,6 +68,17 @@ func (d *Duration) UnmarshalYAML(n *yaml.Node) error {
return nil
}
func (d *Duration) UnmarshalText(text []byte) error {
dur, err := time.ParseDuration(string(text))
if err != nil {
return err
}
*d = Duration(dur)
return nil
}
func (d *Duration) UnmarshalJSON(b []byte) error {
s := strings.Trim(string(b), `"`)
dur, err := time.ParseDuration(s)

View file

@ -8,7 +8,7 @@ import (
"dynatron.me/x/stillbox/pkg/config"
"dynatron.me/x/stillbox/pkg/database"
"github.com/spf13/cobra"
"github.com/urfave/cli/v2"
"golang.org/x/crypto/bcrypt"
"golang.org/x/term"
)
@ -115,59 +115,78 @@ func readPassword(prompt string) (string, error) {
}
// Command is the users command.
func Command(cfg *config.Config) []*cobra.Command {
userCmd := &cobra.Command{
Use: "users",
Aliases: []string{"u"},
Short: "administers the server",
PersistentPreRunE: cfg.PreRunE(),
func Command(cfg *config.Configuration) *cli.Command {
c := &cfg.Config
userCmd := &cli.Command{
Name: "users",
Aliases: []string{"u"},
Usage: "administers users",
Subcommands: []*cli.Command{
addUserCommand(c),
passwdCommand(c),
},
}
userCmd.AddCommand(addUserCommand(cfg), passwdCommand(cfg))
return []*cobra.Command{userCmd}
return userCmd
}
func addUserCommand(cfg *config.Config) *cobra.Command {
c := &cobra.Command{
Use: "add",
Short: "adds a user",
RunE: func(cmd *cobra.Command, args []string) error {
func addUserCommand(cfg *config.Config) *cli.Command {
c := &cli.Command{
Name: "add",
Description: "adds a user",
UsageText: "stillbox users add [-a] [-m email] [username]",
Args: true,
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() != 1 {
return errors.New(ctx.Command.Usage)
}
db, err := database.NewClient(context.Background(), cfg.DB)
if err != nil {
return err
}
username := args[0]
isAdmin, err := cmd.Flags().GetBool("admin")
if err != nil {
return err
}
email, err := cmd.Flags().GetString("email")
if err != nil {
return err
}
username := ctx.Args().Get(0)
isAdmin := ctx.Bool("admin")
email := ctx.String("email")
return AddUser(database.CtxWithDB(context.Background(), db), username, email, isAdmin)
},
Args: cobra.ExactArgs(1),
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "admin",
Aliases: []string{"a"},
Value: false,
Usage: "user is an admin",
},
&cli.StringFlag{
Name: "email",
Usage: "email address",
Aliases: []string{"m"},
},
},
}
c.Flags().BoolP("admin", "a", false, "is admin")
c.Flags().StringP("email", "m", "", "email address")
return c
}
func passwdCommand(cfg *config.Config) *cobra.Command {
c := &cobra.Command{
Use: "passwd userid",
Short: "changes password for a user",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
func passwdCommand(cfg *config.Config) *cli.Command {
c := &cli.Command{
Name: "passwd",
Usage: "changes password for a user",
UsageText: "stillbox users passwd [username]",
Args: true,
Action: func(ctx *cli.Context) error {
if ctx.Args().Len() != 1 {
return errors.New(ctx.Command.Usage)
}
db, err := database.NewClient(context.Background(), cfg.DB)
if err != nil {
return err
}
username := args[0]
username := ctx.Args().Get(0)
return Passwd(database.CtxWithDB(context.Background(), db), username)
},
}

View file

@ -9,32 +9,31 @@ import (
"dynatron.me/x/stillbox/pkg/config"
"dynatron.me/x/stillbox/pkg/server"
"github.com/spf13/cobra"
"github.com/urfave/cli/v2"
)
type ServeOptions struct {
cfg *config.Config
cfg *config.Configuration
}
func Command(cfg *config.Config) *cobra.Command {
func Command(cfg *config.Configuration) *cli.Command {
opts := makeOptions(cfg)
serveCmd := &cobra.Command{
Use: "serve",
Short: "starts the" + common.AppName + " server",
PersistentPreRunE: cfg.PreRunE(),
RunE: common.RunE(opts),
serveCmd := &cli.Command{
Name: "serve",
Usage: "starts the " + common.AppName + " server",
Action: common.Action(opts),
}
return serveCmd
}
func makeOptions(cfg *config.Config) *ServeOptions {
func makeOptions(cfg *config.Configuration) *ServeOptions {
return &ServeOptions{
cfg: cfg,
}
}
func (o *ServeOptions) Options(_ *cobra.Command, args []string) error {
func (o *ServeOptions) Options(_ *cli.Context) error {
return nil
}

View file

@ -1,17 +1,20 @@
package config
import (
"os"
"sync"
"time"
"dynatron.me/x/stillbox/internal/jsontypes"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"
)
type Configuration struct {
Config
configPath *string `yaml:"-"`
}
type Config struct {
DB DB `yaml:"db"`
CORS CORS `yaml:"cors"`
@ -23,8 +26,6 @@ type Config struct {
RateLimit RateLimit `yaml:"rateLimit"`
Notify Notify `yaml:"notify"`
Relay []Relay `yaml:"relay"`
configPath string
}
type Auth struct {
@ -92,33 +93,3 @@ func (rl *RateLimit) Verify() bool {
return false
}
func (c *Config) PreRunE() func(*cobra.Command, []string) error {
return func(cmd *cobra.Command, args []string) error {
return c.ReadConfig()
}
}
func New(rootCommand *cobra.Command) *Config {
c := &Config{}
rootCommand.PersistentFlags().StringVarP(&c.configPath, "config", "c", "config.yaml", "configuration file")
return c
}
func (c *Config) ReadConfig() error {
cfgBytes, err := os.ReadFile(c.configPath)
if err != nil {
return err
}
err = yaml.Unmarshal(cfgBytes, c)
if err != nil {
return err
}
log.Info().Str("configPath", c.configPath).Msg("read config")
return nil
}

66
pkg/config/parse.go Normal file
View file

@ -0,0 +1,66 @@
package config
import (
"fmt"
"strings"
"dynatron.me/x/stillbox/internal/common"
"github.com/go-viper/mapstructure/v2"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/rs/zerolog/log"
"github.com/urfave/cli/v2"
)
func New(configFile *string) *Configuration {
if configFile == nil {
panic("configFile must not be nil")
}
return &Configuration{configPath: configFile}
}
func (c *Configuration) Before(ctx *cli.Context) error {
return c.ReadConfig()
}
func (c *Configuration) ReadConfig() error {
log.Info().Str("configPath", *c.configPath).Msg("read config")
return c.read()
}
func (c *Configuration) read() error {
k := koanf.New(".")
err := k.Load(file.Provider(*c.configPath), yaml.Parser())
if err != nil {
return err
}
k.Load(env.Provider(common.EnvPrefix, ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, common.EnvPrefix)), "_", ".", -1)
}), nil)
err = k.UnmarshalWithConf("", &c.Config,
koanf.UnmarshalConf{
Tag: "yaml",
DecoderConfig: &mapstructure.DecoderConfig{
Result: &c.Config,
WeaklyTypedInput: true,
DecodeHook: mapstructure.ComposeDecodeHookFunc(
mapstructure.StringToTimeDurationHookFunc(),
mapstructure.TextUnmarshallerHookFunc(),
),
},
})
if err != nil {
return fmt.Errorf("unmarshal err: %w", err)
}
return nil
}

80
pkg/config/parse_test.go Normal file
View file

@ -0,0 +1,80 @@
package config
import (
"testing"
"time"
"dynatron.me/x/stillbox/internal/common"
"dynatron.me/x/stillbox/internal/jsontypes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var expCfg = &Config{
DB: DB{
Connect: "postgres://stillbox:somepassword@stillbox:5432/stillbox?sslmode=disable",
LogQueries: true,
},
CORS: CORS{
AllowedOrigins: []string{
"http://localhost:*",
},
},
Auth: Auth{
JWTSecret: "somesecret",
Domain: "xenon",
AllowInsecure: map[string]bool{
"localhost": true,
"stillbox": true,
},
},
Alerting: Alerting{
Enable: true,
LookbackDays: 7,
HalfLife: jsontypes.Duration(30 * time.Minute),
Recent: jsontypes.Duration(2 * time.Hour),
AlertThreshold: 0.3,
Renotify: common.PtrTo(jsontypes.Duration(30 * time.Minute)),
},
Log: []Logger{
Logger{
Level: common.PtrTo("debug"),
},
Logger{
Level: common.PtrTo("error"),
File: common.PtrTo("error.log"),
},
},
Listen: ":3051",
Public: true,
RateLimit: RateLimit{
Enable: true,
Requests: 200,
Over: 2 * time.Minute,
},
Notify: Notify{
NotifyService{
Provider: "slackwebhook",
Config: map[string]interface{}{
"webhookURL": "https://hook",
},
},
},
Relay: []Relay{
{
URL: "http://relay",
APIKey: "secret",
Required: true,
},
},
}
func TestConfigParse(t *testing.T) {
c := &Configuration{configPath: common.PtrTo("testdata/testconfig.yaml")}
err := c.read()
require.NoError(t, err)
assert.Equal(t, expCfg, &c.Config)
}

38
pkg/config/testdata/testconfig.yaml vendored Normal file
View file

@ -0,0 +1,38 @@
db:
connect: 'postgres://stillbox:somepassword@stillbox:5432/stillbox?sslmode=disable'
logQueries: true
cors:
allowedOrigins:
- 'http://localhost:*'
auth:
jwtsecret: 'somesecret'
domain: xenon
allowInsecureFor:
"localhost": true
"stillbox": true
listen: ':3051'
public: true
log:
- level: debug
- level: error
file: error.log
rateLimit:
enable: true
requests: 200
over: 2m
alerting:
enable: true
lookbackDays: 7
halfLife: 30m
recent: 2h
alertThreshold: 0.3
renotify: 30m
notify:
- provider: slackwebhook
config:
webhookURL: "https://hook"
relay:
- url: "http://relay"
apiKey: "secret"
required: true

View file

@ -27,7 +27,7 @@ const shutdownTimeout = 5 * time.Second
type Server struct {
auth *auth.Auth
conf *config.Config
conf *config.Configuration
db database.Store
r *chi.Mux
sources sources.Sources
@ -42,7 +42,7 @@ type Server struct {
rest rest.API
}
func New(ctx context.Context, cfg *config.Config) (*Server, error) {
func New(ctx context.Context, cfg *config.Configuration) (*Server, error) {
logger, err := NewLogger(cfg.Log)
if err != nil {
return nil, err

View file

@ -34,7 +34,7 @@ func (s *Server) installHupHandler() {
hs := s.huppers()
for _, h := range hs {
h.HUP(s.conf)
h.HUP(&s.conf.Config)
}
}
}()