Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: world.toml config file and set namespace #16

Merged
merged 9 commits into from
Nov 28, 2023
21 changes: 21 additions & 0 deletions cmd/world/cardinal/cardinal.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package cardinal

import (
"errors"

"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"pkg.world.dev/world-cli/common/config"
"pkg.world.dev/world-cli/common/dependency"
"pkg.world.dev/world-cli/tea/style"
)

func init() {
// Register subcommands - `world cardinal [subcommand]`
BaseCmd.AddCommand(createCmd, startCmd, devCmd, restartCmd, purgeCmd, stopCmd)
BaseCmd.Flags().String("config", "", "a toml encoded config file")
}

// BaseCmd is the base command for the cardinal subcommand
Expand All @@ -34,3 +38,20 @@ var BaseCmd = &cobra.Command{
}
},
}

func getConfig(cmd *cobra.Command) (cfg config.Config, err error) {
if !cmd.Flags().Changed("config") {
// The config flag was not set. Attempt to find the config via environment variables or in the local directory
return config.LoadConfig("")
}
// The config flag was explicitly set
configFile, err := cmd.Flags().GetString("config")
if err != nil {
return cfg, err
}
if configFile == "" {
return cfg, errors.New("config cannot be empty")
}
return config.LoadConfig(configFile)

}
8 changes: 7 additions & 1 deletion cmd/world/cardinal/restart.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,13 @@ This will restart the following Docker services:
- Cardinal (Core game logic)
- Nakama (Relay)`,
RunE: func(cmd *cobra.Command, args []string) error {
err := tea_cmd.DockerRestart(true, []tea_cmd.DockerService{
cfg, err := getConfig(cmd)
if err != nil {
return err
}
cfg.Build = true

err = tea_cmd.DockerRestart(cfg, []tea_cmd.DockerService{
tea_cmd.DockerServiceCardinal,
tea_cmd.DockerServiceNakama,
})
Expand Down
42 changes: 33 additions & 9 deletions cmd/world/cardinal/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cardinal

import (
"fmt"

"github.com/spf13/cobra"
"pkg.world.dev/world-cli/common/tea_cmd"
)
Expand All @@ -10,10 +11,16 @@ import (
// Cobra Setup //
/////////////////

const (
flagBuild = "build"
flagDebug = "debug"
flagDetach = "detach"
)

func init() {
startCmd.Flags().Bool("build", true, "Rebuild Docker images before starting")
startCmd.Flags().Bool("debug", false, "Run in debug mode")
startCmd.Flags().Bool("detach", false, "Run in detached mode")
startCmd.Flags().Bool(flagBuild, true, "Rebuild Docker images before starting")
startCmd.Flags().Bool(flagDebug, false, "Run in debug mode")
startCmd.Flags().Bool(flagDetach, false, "Run in detached mode")
}

// startCmd starts your Cardinal game shard stack
Expand All @@ -29,30 +36,47 @@ This will start the following Docker services and its dependencies:
- Redis (Cardinal dependency)
- Postgres (Nakama dependency)`,
RunE: func(cmd *cobra.Command, args []string) error {
buildFlag, err := cmd.Flags().GetBool("build")
cfg, err := getConfig(cmd)
if err != nil {
return err
}
// Parameters set at the command line overwrite toml values
if replaceBoolWithFlag(cmd, flagBuild, &cfg.Build); err != nil {
return err
}

debugFlag, err := cmd.Flags().GetBool("debug")
if err != nil {
if replaceBoolWithFlag(cmd, flagDebug, &cfg.Debug); err != nil {
return err
}

detachFlag, err := cmd.Flags().GetBool("detach")
if err != nil {
if replaceBoolWithFlag(cmd, flagDetach, &cfg.Detach); err != nil {
return err
}
cfg.Timeout = -1

fmt.Println("Starting Cardinal game shard...")
fmt.Println("This may take a few minutes to rebuild the Docker images.")
fmt.Println("Use `world cardinal dev` to run Cardinal faster/easier in development mode.")

err = tea_cmd.DockerStartAll(buildFlag, debugFlag, detachFlag, -1)
err = tea_cmd.DockerStartAll(cfg)
if err != nil {
return err
}

return nil
},
}

// replaceBoolWithFlag overwrites the contents of vale with the contents of the given flag. If the flag
// has not been set, value will remain unchanged.
func replaceBoolWithFlag(cmd *cobra.Command, flagName string, value *bool) error {
if !cmd.Flags().Changed(flagName) {
return nil
}
newVal, err := cmd.Flags().GetBool(flagName)
if err != nil {
return err
}
*value = newVal
return nil
}
102 changes: 102 additions & 0 deletions common/config/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package config

import (
"errors"
"fmt"
"os"
"path"
"path/filepath"

"github.com/pelletier/go-toml"
"github.com/rs/zerolog/log"
)

const (
WorldCLIConfigFileEnvVariable = "WORLD_CLI_CONFIG_FILE"
WorldCLIConfigFilename = "world.toml"
)

var (
// Items under these toml headers will be included in the environment variables when
// running docker. An error will be generated if a duplicate key is found across
// these sections.
dockerEnvHeaders = []string{"cardinal", "evm"}
)

type Config struct {
RootDir string
Detach bool
Build bool
Debug bool
Timeout int
DockerEnv map[string]string
}

func LoadConfig(filename string) (Config, error) {
if filename != "" {
return loadConfigFromFile(filename)
}
// Was the file set as an environment variable
if filename = os.Getenv(WorldCLIConfigFileEnvVariable); filename != "" {
return loadConfigFromFile(filename)
}
// Is there a config in this local directory?
currDir, err := os.Getwd()
if err != nil {
return Config{}, err
}

for {
filename = path.Join(currDir, WorldCLIConfigFilename)
if cfg, err := loadConfigFromFile(filename); err == nil {
return cfg, nil
} else if !os.IsNotExist(err) {
return cfg, err
}
before := currDir
currDir = path.Join(currDir, "..")
if currDir == before {
break
}
}

return Config{}, errors.New("no config file found")
}

func loadConfigFromFile(filename string) (Config, error) {
cfg := Config{
DockerEnv: map[string]string{},
}
file, err := os.Open(filename)
if err != nil {
return cfg, err
}
defer file.Close()

data := map[string]any{}
if err = toml.NewDecoder(file).Decode(&data); err != nil {
return cfg, err
}
if rootDir, ok := data["root_dir"]; ok {
cfg.RootDir = rootDir.(string)
} else {
cfg.RootDir, _ = filepath.Split(filename)
}

for _, header := range dockerEnvHeaders {
m, ok := data[header]
if !ok {
continue
}
for key, val := range m.(map[string]any) {
if _, ok := cfg.DockerEnv[key]; ok {
return cfg, fmt.Errorf("duplicate env variable %q", key)
}
cfg.DockerEnv[key] = fmt.Sprintf("%v", val)
}
}

log.Debug().Msgf("successfully loaded config from %q", filename)

return cfg, nil
}
Loading