Created
November 7, 2022 11:53
-
-
Save jxsl13/14aa6c340b2c11d0be01ee099fc21375 to your computer and use it in GitHub Desktop.
urfave/cli and knadh/koanf integration v1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package config | |
import ( | |
"errors" | |
"fmt" | |
"log" | |
"net/url" | |
"os" | |
"strings" | |
"sync" | |
"github.com/go-playground/validator/v10" | |
"github.com/knadh/koanf" | |
"github.com/knadh/koanf/parsers/dotenv" | |
"github.com/knadh/koanf/providers/env" | |
"github.com/knadh/koanf/providers/file" | |
"github.com/knadh/koanf/providers/posflag" | |
"github.com/knadh/koanf/providers/structs" | |
"github.com/spf13/pflag" | |
"github.com/urfave/cli/v2" | |
) | |
var ( | |
cfg *config | |
mu sync.Mutex | |
) | |
type config struct { | |
Username string `koanf:"username"` | |
Password string `koanf:"password"` | |
ClientId string `koanf:"client.id"` | |
ClientSecret string `koanf:"client.secret"` | |
TokenUrl string `koanf:"token.url" validate:"required,url"` | |
Insecure bool `koanf:"insecure"` | |
} | |
func Parse(app *cli.App) error { | |
c := &config{ | |
ClientId: "public", | |
TokenUrl: "https://example.com/auth/realms/REALM/protocol/openid-connect/token", | |
} | |
mu.Lock() | |
defer mu.Unlock() | |
const ( | |
prefix = "PREFIX_" | |
delimiter = "." | |
tag = "koanf" | |
flatStruct = true | |
helpFlag = "help" | |
configFlag = "config" | |
) | |
k := koanf.New(delimiter) | |
transform := func(s string) string { | |
return strings.ToLower(strings.ReplaceAll(s, "_", delimiter)) | |
} | |
f := func(s string) string { | |
return transform(strings.TrimPrefix(s, prefix)) | |
} | |
err := k.Load(structs.ProviderWithDelim(c, tag, delimiter), nil) | |
if err != nil { | |
return err | |
} | |
err = k.Load(env.Provider(prefix, delimiter, f), nil) | |
if err != nil { | |
return err | |
} | |
fs := pflag.NewFlagSet("", pflag.ContinueOnError) | |
fs.BoolP(helpFlag, "h", false, "in order to print this screen") | |
fs.StringP(configFlag, "c", "", ".env configuration file path") | |
for k, v := range k.All() { | |
k = strings.ReplaceAll(k, delimiter, "-") | |
switch x := v.(type) { | |
case bool: | |
fs.Bool(k, x, "") | |
// prevent duplicate flag definitions | |
if !containsFlag(k, app.Flags) { | |
// make flags known to cli framework | |
app.Flags = append(app.Flags, &cli.BoolFlag{ | |
Name: k, | |
Value: x, | |
}) | |
} | |
default: | |
value := fmt.Sprintf("%v", v) | |
fs.String(k, value, "") | |
// prevent duplicate flag definitions | |
if !containsFlag(k, app.Flags) { | |
// make flags known to cli framework | |
app.Flags = append(app.Flags, &cli.StringFlag{ | |
Name: k, | |
Value: value, | |
}) | |
} | |
} | |
} | |
passAppFlagsDown(app) | |
err = fs.Parse(os.Args) | |
if err != nil { | |
return fmt.Errorf("failed to parse config flags: %w", err) | |
} | |
flagK := koanf.New(delimiter) | |
err = flagK.Load( | |
posflag.ProviderWithValue( | |
fs, | |
delimiter, | |
nil, | |
func(key, value string) (string, interface{}) { | |
return transform(key), value | |
}, | |
), nil) | |
if err != nil { | |
return err | |
} | |
if flagK.Bool(helpFlag) { | |
return nil | |
} | |
configPath := flagK.String(configFlag) | |
if configPath != "" { | |
err = k.Load(file.Provider(configPath), dotenv.ParserEnv(prefix, delimiter, f)) | |
if err != nil { | |
return err | |
} | |
} | |
err = k.Merge(flagK) | |
if err != nil { | |
return err | |
} | |
err = k.UnmarshalWithConf("", &c, koanf.UnmarshalConf{ | |
FlatPaths: flatStruct, | |
}) | |
if err != nil { | |
return err | |
} | |
err = c.Validate() | |
if err != nil { | |
return err | |
} | |
cfg = c | |
return nil | |
} | |
func (c *config) Validate() error { | |
if c.Username == "" && c.Password == "" && c.ClientSecret == "" { | |
return errors.New("missing credentials, either username and password or client id and client secret") | |
} | |
return validator.New().Struct(c) | |
} | |
func (c *config) Form() form.Form { | |
if c.Username == "" && c.Password == "" { | |
return form.ClientCredentialsAccessToken(c.ClientId, c.ClientSecret) | |
} else if c.ClientId != "" { | |
return form.PasswordAccessToken(c.Username, c.Password, c.ClientId, c.ClientSecret) | |
} else { | |
return form.PasswordAccessToken(c.Username, c.Password, "public", "") | |
} | |
} | |
func (c *config) IsInsecure() bool { | |
return c.Insecure | |
} | |
func (c *config) KeycloakTokenUrl() *url.URL { | |
u, err := url.ParseRequestURI(c.TokenUrl) | |
if err != nil { | |
log.Fatalf("invalid keycloak token url: %s: %v\n", c.TokenUrl, err) | |
} | |
return u | |
} | |
func containsFlag(name string, flags []cli.Flag) bool { | |
for _, f := range flags { | |
for _, knownName := range f.Names() { | |
if name == knownName { | |
return true | |
} | |
} | |
} | |
return false | |
} | |
func passAppFlagsDown(app *cli.App) { | |
trickleDownFlags(app.Flags, app.Commands) | |
} | |
func trickleDownFlags(parentFlags []cli.Flag, children []*cli.Command) { | |
if len(children) == 0 { | |
// parent is a leaf | |
return | |
} | |
for _, cmd := range children { | |
if len(parentFlags) > 0 { | |
cmd.Flags = append(cmd.Flags, parentFlags...) | |
} | |
trickleDownFlags(cmd.Flags, cmd.Subcommands) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment