Skip to content

Commit

Permalink
Merge pull request #25 from evryn/feat/support-more-config-methods
Browse files Browse the repository at this point in the history
[Feature] Support more config methods
  • Loading branch information
AmirrezaNasiri authored Sep 2, 2023
2 parents 45d454a + 1619ce2 commit 2ebfb2f
Show file tree
Hide file tree
Showing 19 changed files with 277 additions and 118 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/image.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
ghcr.io/evryn/kermoo
quay.io/evryn/kermoo
flavor: |
latest=true
latest=auto
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
Expand Down
11 changes: 9 additions & 2 deletions commands/start_command.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@ func GetStartCommand() *cobra.Command {
Use: "start",
Short: "Start the Kermoo foreground service",
Run: func(cmd *cobra.Command, args []string) {
config, _ := cmd.Flags().GetString("config")
filename, _ := cmd.Flags().GetString("filename")
verbosity, _ := cmd.Flags().GetString("verbosity")

logger.MustInitLogger(verbosity)

user_config.MustLoadProvidedConfig(filename)
if filename != "" {
logger.Log.Warn("`filename` flag is deprecated and will be removed in a future major release. use `config` flag instead.")
config = filename
}

user_config.MustLoadPreparedConfig(config)

user_config.Prepared.Start()

Expand All @@ -29,7 +35,8 @@ func GetStartCommand() *cobra.Command {
},
}

cmd.Flags().StringP("filename", "f", "", "Path to your .yaml or .json configuration file")
cmd.Flags().StringP("filename", "f", "", "Alias to `config` flag")
cmd.Flags().StringP("config", "c", "", "Your YAML or JSON config content or path to a config file")
cmd.Flags().StringP("verbosity", "v", "", "Verbosity level of logging output. Valid values are: debug, info")

return cmd
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ services:
image: evryn/kermoo
command: start
volumes:
- "./config.yaml:/home/kerm/.kermoo/config.yaml"
- "./config.yaml:/home/kerm/.kermoo/config.yaml"
13 changes: 13 additions & 0 deletions docs/examples/docker-compose/inline/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3.7'

services:
kermoo:
image: evryn/kermoo
command:
- start
- -f
- |
process:
exit:
after: 2s
code: 5
File renamed without changes.
File renamed without changes.
13 changes: 13 additions & 0 deletions docs/examples/docker-swarm-inline/inline/stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
version: '3.7'

services:
kermoo:
image: evryn/kermoo
command:
- start
- -f
- |
process:
exit:
after: 2s
code: 5
130 changes: 92 additions & 38 deletions modules/user_config/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,65 +8,65 @@ import (
"kermoo/modules/utils"
"os"
"os/user"
"strings"

"go.uber.org/zap"
)

func MustLoadProvidedConfig(filename string) {
uc, err := LoadUserConfig(filename)
func MustLoadPreparedConfig(config string) {
prepared, err := MakePreparedConfig(config)

if err != nil {
logger.Log.Fatal("invalid initial user-provided config", zap.Error(err))
logger.Log.Panic(err.Error())
}

logger.Log.Info("initial configuration is loaded", zap.Any("unprepared", *uc))
Prepared = *prepared
}

prepared, err := uc.GetPreparedConfig()
// MakePreparedConfig resolves the configuration content and prepares the config object in the following order:
//
// 1. When no config is provided, it autoloads it from `{userHome}/.kermoo/config.[yaml|yml|json]`
//
// 2. When it couldn't autoload from file, it tries to load from `KERMOO_CONFIG` environment variable
//
// 3. When config is given and equals to "-", it tries to read from stdin (Standard Input) pipe
//
// 4. When the config is a file path, it tries to load it from that file.
//
// 5. Otherwise, it considers the content of config as the actual config and tries to parse that.
func MakePreparedConfig(config string) (*PreparedConfigType, error) {
uc, err := makeUserConfig(config)

if err != nil {
logger.Log.Fatal("invalid prepared user-provided config", zap.Error(err))
return nil, fmt.Errorf("invalid config: %v", err)
}

logger.Log.Info("prepared configuration is loaded", zap.Any("prepared", prepared))
prepared, err := uc.GetPreparedConfig()

Prepared = *prepared
if err != nil {
return nil, fmt.Errorf("unable to preapre parsed config: %v", err)
}

return prepared, nil
}

func LoadUserConfig(filename string) (*UserConfigType, error) {
func makeUserConfig(config string) (*UserConfigType, error) {
var err error
var uc UserConfigType

if filename == "" {
u, err := user.Current()
if err != nil {
return nil, fmt.Errorf("unable to determine current user to autoload config: %v", err)
}
filename = u.HomeDir + "/.kermoo/config.yaml"
}

if filename == "-" {
logger.Log.Debug("loading configuration from stdin...")
config, err = getResolvedConfig(config)

content, err := readStdin()
if err != nil {
return nil, err
}
if err != nil {
return nil, err
}

uc, err = unmarshal(content)
if err != nil {
return nil, err
}
} else {
logger.Log.Debug("loading configuration from file...", zap.String("filename", filename))
content, err := readFile(filename)
if err != nil {
return nil, err
}
if config == "" {
return nil, fmt.Errorf("resolved config is empty")
}

uc, err = unmarshal(content)
if err != nil {
return nil, err
}
uc, err = unmarshal(config)
if err != nil {
return nil, err
}

err = uc.Validate()
Expand All @@ -78,8 +78,58 @@ func LoadUserConfig(filename string) (*UserConfigType, error) {
return &uc, nil
}

func getResolvedConfig(config string) (string, error) {
if config == "" {
return getAutoloadedConfig()
}

if config == "-" {
logger.Log.Debug("loading configuration from stdin...")

return readStdin()
}

// TODO: Change the following to something more sophisticated
if !strings.ContainsAny(config, "\n\t{}") {
return readFile(config)
}

return config, nil
}

func getAutoloadedConfig() (string, error) {
u, err := user.Current()
if err != nil {
return "", fmt.Errorf("unable to determine current user to autoload config: %v", err)
}

pathes := []string{
u.HomeDir + "/.kermoo/config.yaml",
u.HomeDir + "/.kermoo/config.yml",
u.HomeDir + "/.kermoo/config.json",
u.HomeDir + "/.config/kermoo/config.yaml",
u.HomeDir + "/.config/kermoo/config.yml",
u.HomeDir + "/.config/kermoo/config.json",
}

for _, path := range pathes {
content, err := readFile(u.HomeDir + path)

if err == nil {
return content, nil
}
}

content := os.Getenv("KERMOO_CONFIG")
if content != "" {
return content, nil
}

return "", fmt.Errorf("no config is specified so we tried to autoload config but was unable to load it either from default path (%v) or from the environment variable. kermoo can not live without a config :(", u.HomeDir+"/.kermoo/")
}

func readFile(filename string) (string, error) {
logger.Log.Debug("reading file", zap.String("filename", filename))
logger.Log.Debug("reading file ...", zap.String("filename", filename))

body, err := os.ReadFile(filename)
if err != nil {
Expand All @@ -90,6 +140,8 @@ func readFile(filename string) (string, error) {
}

func readStdin() (string, error) {
logger.Log.Debug("reading stdin ...")

stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) != 0 {
return "", fmt.Errorf("stdin is not available to read from")
Expand All @@ -109,6 +161,8 @@ func readStdin() (string, error) {
}

func unmarshal(content string) (UserConfigType, error) {
logger.Log.Debug("unmarshaling config ...", zap.Any("config", content))

firstChar := string(content[0])

// Content is probably JSON
Expand Down
4 changes: 2 additions & 2 deletions tests/e2e/webserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ func TestWebserverEndToEnd(t *testing.T) {
content:
static: hello-world
fault:
interval: 100ms
interval: 50ms
percentage: 50
`, 3*time.Second)

Expand All @@ -237,7 +237,7 @@ func TestWebserverEndToEnd(t *testing.T) {
e2e.Start(`
plans:
- name: readiness
interval: 100ms
interval: 50ms
percentage: 40
webServers:
- port: 8080
Expand Down
3 changes: 1 addition & 2 deletions tests/units/stubs/invalid_structure.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
{
"schemaVersion": "invalid-schema-version",
"process": {
"exit": {
"after": "10ms to 1s100ms",
"after": "something invalid",
"code": 2
}
}
Expand Down
3 changes: 1 addition & 2 deletions tests/units/stubs/invalid_structure.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
schemaVersion: "invalid-schema-version"
process:
exit:
after: 10ms to 1s100ms
after: something invalid
code: 2
1 change: 0 additions & 1 deletion tests/units/stubs/valid.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
{
"schemaVersion": "1",
"process": {
"exit": {
"after": "10ms to 1s100ms",
Expand Down
1 change: 0 additions & 1 deletion tests/units/stubs/valid.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
schemaVersion: "1"
process:
exit:
after: 10ms to 1s100ms
Expand Down
Loading

0 comments on commit 2ebfb2f

Please sign in to comment.