Merge pull request #111 from foomo/feature/multi-configs

feat: multi configs
This commit is contained in:
Kevin Franklin Kim 2025-03-20 16:21:56 +01:00 committed by GitHub
commit 0617588911
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 222 additions and 59 deletions

View File

@ -1,10 +1,14 @@
package cmd
import (
"bytes"
"encoding/json"
"fmt"
"os"
"github.com/alecthomas/chroma/quick"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
"github.com/itchyny/json2yaml"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@ -22,11 +26,15 @@ func NewConfig(root *cobra.Command) {
out, err := json.MarshalIndent(cfg, "", " ")
if err != nil {
return err
return errors.Wrap(err, "failed to marshal config")
}
fmt.Println(string(out))
return nil
var buf bytes.Buffer
if err := json2yaml.Convert(&buf, bytes.NewBuffer(out)); err != nil {
return errors.Wrap(err, "failed to convert config")
}
return quick.Highlight(os.Stdout, buf.String(), "yaml", "terminal", "monokai")
},
}

View File

@ -1,16 +1,45 @@
package cmd
import (
"fmt"
"os"
"runtime/debug"
"strings"
pkgcmd "github.com/foomo/sesamy-cli/pkg/cmd"
cowsay "github.com/Code-Hex/Neo-cowsay/v2"
"github.com/pkg/errors"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var root *cobra.Command
var (
root *cobra.Command
)
func init() {
pterm.Info.Prefix.Text = "⎈"
pterm.Info.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
pterm.Debug.Prefix.Text = "⛏︎"
pterm.Debug.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
pterm.Fatal.Prefix.Text = "⛔︎"
pterm.Fatal.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
pterm.Error.Prefix.Text = "⛌"
pterm.Error.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
pterm.Warning.Prefix.Text = "⚠"
pterm.Warning.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
pterm.Success.Prefix.Text = "✓"
pterm.Success.Scope.Style = &pterm.ThemeDefault.DebugMessageStyle
if scope := os.Getenv("SESAMY_SCOPE"); scope != "" {
pterm.Info.Scope.Text = scope
pterm.Debug.Scope.Text = scope
pterm.Fatal.Scope.Text = scope
pterm.Error.Scope.Text = scope
pterm.Warning.Scope.Text = scope
pterm.Success.Scope.Text = scope
}
root = NewRoot()
NewConfig(root)
NewVersion(root)
@ -18,20 +47,26 @@ func init() {
NewList(root)
NewProvision(root)
NewTypeScript(root)
cobra.OnInitialize(pkgcmd.InitConfig)
}
// NewRoot represents the base command when called without any subcommands
func NewRoot() *cobra.Command {
cmd := &cobra.Command{
Use: "sesamy",
Short: "Server Side Tag Management System",
Use: "sesamy",
Short: "Server Side Tag Management System",
SilenceErrors: true,
SilenceUsage: true,
PersistentPostRunE: func(cmd *cobra.Command, args []string) error {
if viper.GetBool("verbose") {
pterm.EnableDebugMessages()
}
return nil
},
}
cmd.PersistentFlags().BoolP("verbose", "v", false, "output debug information")
_ = viper.BindPFlag("verbose", cmd.PersistentFlags().Lookup("verbose"))
cmd.PersistentFlags().StringP("config", "c", "sesamy.yaml", "config file (default is sesamy.yaml)")
cmd.PersistentFlags().StringSliceP("config", "c", []string{"sesamy.yaml"}, "config files (default is sesamy.yaml)")
_ = viper.BindPFlag("config", cmd.PersistentFlags().Lookup("config"))
return cmd
}
@ -39,7 +74,28 @@ func NewRoot() *cobra.Command {
// 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() {
say := func(msg string) string {
if say, cerr := cowsay.Say(msg, cowsay.BallonWidth(80)); cerr == nil {
msg = say
}
return msg
}
code := 0
defer func() {
if r := recover(); r != nil {
pterm.Error.Println(say("It's time to panic"))
pterm.Error.Println(fmt.Sprintf("%v", r))
pterm.Error.Println(string(debug.Stack()))
code = 1
}
os.Exit(code)
}()
if err := root.Execute(); err != nil {
os.Exit(1)
pterm.Error.Println(say(strings.Split(errors.Cause(err).Error(), ":")[0]))
pterm.Error.Println(err.Error())
pterm.Error.Println(root.UsageString())
code = 1
}
}

11
go.mod
View File

@ -3,15 +3,19 @@ module github.com/foomo/sesamy-cli
go 1.24.1
require (
github.com/Code-Hex/Neo-cowsay/v2 v2.0.4
github.com/alecthomas/chroma v0.10.0
github.com/fatih/structtag v1.2.0
github.com/foomo/go v0.0.3
github.com/foomo/gocontemplate v0.2.0
github.com/foomo/sesamy-go v0.9.0
github.com/go-viper/mapstructure/v2 v2.2.1
github.com/invopop/jsonschema v0.13.0
github.com/itchyny/json2yaml v0.1.4
github.com/joho/godotenv v1.5.1
github.com/knadh/koanf/parsers/yaml v0.1.0
github.com/knadh/koanf/providers/file v1.1.2
github.com/knadh/koanf/providers/rawbytes v0.1.0
github.com/knadh/koanf/v2 v2.1.2
github.com/pkg/errors v0.9.1
github.com/pterm/pterm v0.12.80
github.com/spf13/cobra v1.9.1
@ -29,6 +33,7 @@ require (
cloud.google.com/go/auth v0.15.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
github.com/Code-Hex/go-wordwrap v1.0.0 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect
github.com/containerd/console v1.0.4 // indirect
@ -39,16 +44,20 @@ require (
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.5 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gookit/color v1.5.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/knadh/koanf/maps v0.1.1 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect

22
go.sum
View File

@ -12,6 +12,10 @@ cloud.google.com/go/auth/oauth2adapt v0.2.7 h1:/Lc7xODdqcEw8IrZ9SvwnlLX6j9FHQM74
cloud.google.com/go/auth/oauth2adapt v0.2.7/go.mod h1:NTbTTzfvPl1Y3V1nPpOgl2w6d/FjO7NNUQaWSox6ZMc=
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
github.com/Code-Hex/Neo-cowsay/v2 v2.0.4 h1:y80Hd9hmB+rsEH/p4c5ti5PbO0PhBmxw4NgbpFZvoHg=
github.com/Code-Hex/Neo-cowsay/v2 v2.0.4/go.mod h1:6k40Pwrc2FazLf1BUbmAC36E9LvT+DErjZr30isbXhg=
github.com/Code-Hex/go-wordwrap v1.0.0 h1:yl5fLyZEz3+hPGbpTRlTQ8mQJ1HXWcTq1FCNR1ch6zM=
github.com/Code-Hex/go-wordwrap v1.0.0/go.mod h1:/SsbgkY2Q0aPQRyvXcyQwWYTQOIwSORKe6MPjRVGIWU=
github.com/MarvinJWendt/testza v0.1.0/go.mod h1:7AxNvlfeHP7Z/hDQ5JtE3OKYT3XFUeLCDE2DQninSqs=
github.com/MarvinJWendt/testza v0.2.1/go.mod h1:God7bhG8n6uQxwdScay+gjm9/LnO4D3kkcZX4hv9Rp8=
github.com/MarvinJWendt/testza v0.2.8/go.mod h1:nwIcjmr0Zz+Rcwfh3/4UhBp7ePKVhuBExvZqnKYWlII=
@ -64,6 +68,7 @@ github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIx
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
@ -91,6 +96,16 @@ github.com/klauspost/cpuid/v2 v2.0.10/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuOb
github.com/klauspost/cpuid/v2 v2.0.12/go.mod h1:g2LTdtYhdyuGPqyWyv7qRAmj1WBqxuObKfj5c0PQa7c=
github.com/klauspost/cpuid/v2 v2.2.3 h1:sxCkb+qR91z4vsqw4vGGZlDgPz3G7gjaLyK3V8y70BU=
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
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/file v1.1.2 h1:aCC36YGOgV5lTtAFz2qkgtWdeQsgfxUkxDOe+2nQY3w=
github.com/knadh/koanf/providers/file v1.1.2/go.mod h1:/faSBcv2mxPVjFrXck95qeoyoZ5myJ6uxN8OOVNJJCI=
github.com/knadh/koanf/providers/rawbytes v0.1.0 h1:dpzgu2KO6uf6oCb4aP05KDmKmAmI51k5pe8RYKQ0qME=
github.com/knadh/koanf/providers/rawbytes v0.1.0/go.mod h1:mMTB1/IcJ/yE++A2iEZbY1MLygX7vttU+C+S/YmPu9c=
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.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -102,11 +117,16 @@ github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4=
github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
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/mapstructure v1.5.1-0.20220423185008-bf980b35cac4 h1:BpfhmLKZf+SjVanKKhCgf3bg+511DmU9eDQTen7LLbY=
github.com/mitchellh/mapstructure v1.5.1-0.20220423185008-bf980b35cac4/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@ -241,6 +261,8 @@ golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.226.0 h1:9A29y1XUD+YRXfnHkO66KggxHBZWg9LsTGqm7TkUvtQ=
google.golang.org/api v0.226.0/go.mod h1:WP/0Xm4LVvMOCldfvOISnWquSRWbG2kArDZcg+W2DbY=
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk=

View File

@ -1,57 +1,51 @@
package cmd
import (
"bytes"
"io"
"log/slog"
"github.com/foomo/sesamy-cli/pkg/config"
"github.com/go-viper/mapstructure/v2"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/rawbytes"
"github.com/knadh/koanf/v2"
"github.com/pkg/errors"
"github.com/pterm/pterm"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
// InitConfig reads in config file and ENV variables if set.
func InitConfig() {
filename := viper.GetString("config")
viper.SetConfigType("yaml")
if filename == "-" {
// do nothing
} else if filename != "" {
// Use config file from the flag.
viper.SetConfigFile(filename)
} else {
// Search config in home directory with name ".sesamy" (without extension).
viper.AddConfigPath(".")
viper.SetConfigName("sesamy")
}
var conf = koanf.Conf{
Delim: "/",
}
var k = koanf.NewWithConf(conf)
func ReadConfig(l *slog.Logger, cmd *cobra.Command) (*config.Config, error) {
filename := viper.GetString("config")
filenames := viper.GetStringSlice("config")
if filename == "-" {
l.Debug("using config from stdin")
b, err := io.ReadAll(cmd.InOrStdin())
if err != nil {
return nil, err
for _, filename := range filenames {
var p koanf.Provider
switch {
case filename == "-":
pterm.Debug.Println("reading config from stdin")
if b, err := io.ReadAll(cmd.InOrStdin()); err != nil {
return nil, err
} else {
p = rawbytes.Provider(b)
}
default:
pterm.Debug.Println("reading config from filename: " + filename)
p = file.Provider(filename)
}
if err := viper.ReadConfig(bytes.NewBuffer(b)); err != nil {
return nil, err
}
} else {
l.Debug("using config file", "filename", viper.ConfigFileUsed())
if err := viper.ReadInConfig(); err != nil {
return nil, err
if err := k.Load(p, yaml.Parser()); err != nil {
return nil, errors.Wrap(err, "error loading config file: "+filename)
}
}
// l.Debug("config", l.ArgsFromMap(viper.AllSettings()))
var cfg *config.Config
if err := viper.Unmarshal(&cfg, func(decoderConfig *mapstructure.DecoderConfig) {
decoderConfig.TagName = "yaml"
pterm.Debug.Println("unmarshalling config")
if err := k.UnmarshalWithConf("", &cfg, koanf.UnmarshalConf{
Tag: "yaml",
}); err != nil {
return nil, errors.Wrap(err, "failed to unmarshal config")
}

85
pkg/cmd/handler.go Normal file
View File

@ -0,0 +1,85 @@
package cmd
import (
"context"
"fmt"
"log/slog"
"github.com/pterm/pterm"
)
type SlogHandler struct {
attrs []slog.Attr
}
// Enabled returns true if the given level is enabled.
func (s *SlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
switch level {
case slog.LevelDebug:
return pterm.PrintDebugMessages
default:
return true
}
}
// Handle handles the given record.
func (s *SlogHandler) Handle(ctx context.Context, record slog.Record) error {
level := record.Level
message := record.Message
// Convert slog Attrs to a map.
keyValsMap := make(map[string]any)
record.Attrs(func(attr slog.Attr) bool {
keyValsMap[attr.Key] = attr.Value
return true
})
for _, attr := range s.attrs {
keyValsMap[attr.Key] = attr.Value
}
args := pterm.DefaultLogger.ArgsFromMap(keyValsMap)
// Wrapping args inside another slice to match [][]LoggerArgument
argsWrapped := [][]pterm.LoggerArgument{args}
for _, arg := range argsWrapped {
for _, attr := range arg {
message += " " + attr.Key + ": " + fmt.Sprintf("%v", attr.Value)
}
}
switch level {
case slog.LevelDebug:
pterm.Debug.Println(message)
case slog.LevelInfo:
pterm.Info.Println(message)
case slog.LevelWarn:
pterm.Warning.Println(message)
case slog.LevelError:
pterm.Error.Println(message)
default:
pterm.Info.Println(message)
}
return nil
}
// WithAttrs returns a new handler with the given attributes.
func (s *SlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newS := *s
newS.attrs = attrs
return &newS
}
// WithGroup is not yet supported.
func (s *SlogHandler) WithGroup(name string) slog.Handler {
// Grouping is not yet supported by pterm.
return s
}
// NewSlogHandler returns a new logging handler that can be intrgrated with log/slog.
func NewSlogHandler() *SlogHandler {
return &SlogHandler{}
}

View File

@ -2,22 +2,11 @@ package cmd
import (
"log/slog"
"github.com/pterm/pterm"
"github.com/spf13/viper"
)
func Logger() *slog.Logger {
verbose := viper.GetBool("verbose")
plogger := pterm.DefaultLogger.WithTime(false).WithMaxWidth(100)
if verbose {
plogger = plogger.WithLevel(pterm.LogLevelTrace).WithCaller(true)
}
// Create a new slog handler with the default PTerm logger
handler := pterm.NewSlogHandler(plogger)
handler := NewSlogHandler()
// Create a new slog logger with the handler
return slog.New(handler)

View File

@ -26,7 +26,7 @@ func LoadEventParams(ctx context.Context, cfg contemplate.Config) (map[string]ma
for _, typ := range cfgPkg.Types {
eventParams, err := getEventParams(pkg.LookupScopeType(typ))
if err != nil {
return nil, errors.Wrap(err, "failed to load event params: "+typ)
return nil, errors.Wrap(err, "failed to load event params: "+cfgPkg.Path+"."+typ)
}
ret[strcase.SnakeCase(typ)] = eventParams
}