Merge pull request 'Move to koanf and urfavecli' (#50) from urfavecli into trunk
Reviewed-on: #50
This commit is contained in:
commit
c872f7bb9a
14 changed files with 352 additions and 122 deletions
|
@ -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")
|
||||
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
15
go.mod
|
@ -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
30
go.sum
|
@ -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=
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
internal/forms/testdata/urlenc3.http
vendored
2
internal/forms/testdata/urlenc3.http
vendored
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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",
|
||||
func Command(cfg *config.Configuration) *cli.Command {
|
||||
c := &cfg.Config
|
||||
userCmd := &cli.Command{
|
||||
Name: "users",
|
||||
Aliases: []string{"u"},
|
||||
Short: "administers the server",
|
||||
PersistentPreRunE: cfg.PreRunE(),
|
||||
}
|
||||
userCmd.AddCommand(addUserCommand(cfg), passwdCommand(cfg))
|
||||
|
||||
return []*cobra.Command{userCmd}
|
||||
Usage: "administers users",
|
||||
Subcommands: []*cli.Command{
|
||||
addUserCommand(c),
|
||||
passwdCommand(c),
|
||||
},
|
||||
}
|
||||
|
||||
return userCmd
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func addUserCommand(cfg *config.Config) *cobra.Command {
|
||||
c := &cobra.Command{
|
||||
Use: "add",
|
||||
Short: "adds a user",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
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)
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
66
pkg/config/parse.go
Normal 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
80
pkg/config/parse_test.go
Normal 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
38
pkg/config/testdata/testconfig.yaml
vendored
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -34,7 +34,7 @@ func (s *Server) installHupHandler() {
|
|||
|
||||
hs := s.huppers()
|
||||
for _, h := range hs {
|
||||
h.HUP(s.conf)
|
||||
h.HUP(&s.conf.Config)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
|
Loading…
Reference in a new issue