From dfdcc7773702efd430c02f4b0be3dcaf65f13806 Mon Sep 17 00:00:00 2001 From: leo Date: Thu, 4 May 2023 23:49:25 +0200 Subject: [PATCH] conf: add pretty printing,raw conf, conf tests * also, set debug logger directly in config if devel mode is set * add new flag to indicate whether the passed config is path or raw config --- config/config.go | 69 +++++++++++++++++++++- config/config_test.go | 114 +++++++++++++++++++++++++++++++++++++ config/testconfigs/1.dhall | 9 +++ config/testconfigs/2.dhall | 9 +++ run.go | 19 ++++--- 5 files changed, 208 insertions(+), 12 deletions(-) create mode 100644 config/config_test.go create mode 100644 config/testconfigs/1.dhall create mode 100644 config/testconfigs/2.dhall diff --git a/config/config.go b/config/config.go index 148d1fb..14cf1a0 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,12 @@ package config import ( + "context" + "fmt" + "os" + "os/exec" + "time" + "git.dotya.ml/mirre-mt/pcmt/slogging" "github.com/philandstuff/dhall-golang/v6" ) @@ -15,15 +21,72 @@ type Config struct { SessionCookieSecret string } -func LoadConfig(path string) (*Config, error) { +func LoadConfig(conf string, isPath bool) (*Config, error) { var config Config - err := dhall.UnmarshalFile(path, &config) + var err error + + if isPath { + slogging.GetLogger().Debug("config from file") + + err = dhall.UnmarshalFile(conf, &config) + } else { + slogging.GetLogger().Debug("config raw from cmdline") + + err = dhall.Unmarshal([]byte(conf), &config) + } + if err != nil { return nil, err } - slogging.GetLogger().Debugf("parsed config: %+v", config) + if config.DevelMode { + _ = slogging.SetLevel(slogging.LevelDebug) + + slogging.GetLogger().Debugf("parsed config: %+v", config) + + if dhallCmdExists() { + _ = prettyPrintConfig(conf, isPath) + } + } return &config, nil } + +func prettyPrintConfig(conf string, isPath bool) error { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + var cmd *exec.Cmd + if isPath { + cmd = exec.CommandContext(ctx, "/bin/sh", "-c", "dhall --file "+conf) //nolint:gosec + } else { + cmd = exec.CommandContext(ctx, "/bin/sh", "-c", "dhall <<< \""+conf+"\"") //nolint:gosec + } + + output, err := cmd.CombinedOutput() + if err != nil { + slogging.GetLogger().Debug("could not pretty-print config", "error", err) + return err + } + + if isPath { + fmt.Fprintln(os.Stderr, "\n"+conf+":\n"+string(output)) + } else { + fmt.Fprintln(os.Stderr, "\nconfig:\n"+string(output)) + } + + return nil +} + +func dhallCmdExists() bool { + ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) + defer cancel() + + if err := exec.CommandContext(ctx, "/bin/sh", "-c", "command -v dhall").Run(); err != nil { + slogging.GetLogger().Debug("no command dhall") + return false + } + + return true +} diff --git a/config/config_test.go b/config/config_test.go new file mode 100644 index 0000000..ce7c19b --- /dev/null +++ b/config/config_test.go @@ -0,0 +1,114 @@ +package config + +import ( + "os" + "testing" + + "git.dotya.ml/mirre-mt/pcmt/slogging" +) + +func TestConfig(t *testing.T) { + if os.Getenv("CI") == "true" { + t.Skip("we're running in CI apparently, skipping these tests") + } + + confPath := "./testconfigs/" + ts := []struct { + name string + fails bool + isPath bool + conf string + }{ + { + // incorrect type of the `Host` field. + name: "1.dhall", + fails: true, + isPath: true, + conf: "1.dhall", + }, + { + name: "2.dhall", + fails: false, + isPath: true, + conf: "2.dhall", + }, + { + name: "3.dhall", + fails: false, + isPath: false, + conf: ` + let ConfigSchema = + https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + sha256:ad7ba86d5d388a99b7543faa0e4c81ba1d9b78fa6c32fdaf4ac4477089d177be + ? https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + + let Config = ConfigSchema.Schema + + let c = Config::{ DevelMode = True } + + in c + `, + }, + { + // misses final `in`. + name: "4.dhall", + fails: true, + isPath: false, + conf: ` + let ConfigSchema = + https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + sha256:ad7ba86d5d388a99b7543faa0e4c81ba1d9b78fa6c32fdaf4ac4477089d177be + ? https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + + let Config = ConfigSchema.Schema + + let c = Config::{ DevelMode = True } + `, + }, + { + name: "5.dhall", + fails: false, + isPath: false, + conf: ` + let ConfigSchema = + https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + in ConfigSchema.Schema::{ DevelMode = True, Port = 5555 } + `, + }, + { + name: "6.dhall", + fails: false, + isPath: false, + conf: ` + let ConfigSchema = + https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + in ConfigSchema.Schema.default // { DevelMode = True } + `, + }, + } + + _ = slogging.Init(false) + + for _, tc := range ts { + t.Logf("running test case %s", tc.name) + + var err error + + shouldFail := tc.fails + + if tc.isPath { + _, err = LoadConfig(confPath+tc.conf, tc.isPath) + } else { + _, err = LoadConfig(tc.conf, tc.isPath) + } + + switch { + case err == nil && shouldFail: + t.Errorf("test case '%s' should have failed", tc.name) + + case err != nil && !shouldFail: + t.Log(err) + t.Errorf("test case '%s' should not have failed", tc.name) + } + } +} diff --git a/config/testconfigs/1.dhall b/config/testconfigs/1.dhall new file mode 100644 index 0000000..a1fdc69 --- /dev/null +++ b/config/testconfigs/1.dhall @@ -0,0 +1,9 @@ +let ConfigSchema = + https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + sha256:ad7ba86d5d388a99b7543faa0e4c81ba1d9b78fa6c32fdaf4ac4477089d177be + +let Config = ConfigSchema.Schema + +let c = Config::{ Host = False } + +in c diff --git a/config/testconfigs/2.dhall b/config/testconfigs/2.dhall new file mode 100644 index 0000000..624a527 --- /dev/null +++ b/config/testconfigs/2.dhall @@ -0,0 +1,9 @@ +let ConfigSchema = + https://git.dotya.ml/mirre-mt/pcmt/raw/branch/development/config/schema/package.dhall + sha256:ad7ba86d5d388a99b7543faa0e4c81ba1d9b78fa6c32fdaf4ac4477089d177be + +let Config = ConfigSchema.Schema + +let c = Config::{ Host = "localhost" } + +in c diff --git a/run.go b/run.go index 7200afd..bb48d57 100644 --- a/run.go +++ b/run.go @@ -51,13 +51,14 @@ along with this program. If not, see .` ) var ( - host = flag.String("host", "unset", "host address to listen on") - port = flag.Int("port", -1, "TCP port to listen on") - configFlag = flag.String("config", "config.dhall", "Default path of the config file") - devel = flag.Bool("devel", false, "Run the application in dev mode, connect to a local browser-sync instance for hot-reloading") - license = flag.Bool("license", false, "Print licensing information and exit") - version = "dev" - log *slogging.Logger + host = flag.String("host", "unset", "host address to listen on") + port = flag.Int("port", -1, "TCP port to listen on") + configFlag = flag.String("config", "config.dhall", "Default path of the config file") + configIsPathFlag = flag.Bool("configIsPath", true, "Whether the provided config is path or raw config") + devel = flag.Bool("devel", false, "Run the application in dev mode, connect to a local browser-sync instance for hot-reloading") + license = flag.Bool("license", false, "Print licensing information and exit") + version = "dev" + log *slogging.Logger ) func run() error { @@ -81,9 +82,9 @@ func run() error { // TODO: SBOM: https://actuated.dev/blog/sbom-in-github-actions // TODO: SBOM: https://www.docker.com/blog/generate-sboms-with-buildkit/ - conf, err := config.LoadConfig(*configFlag) + conf, err := config.LoadConfig(*configFlag, *configIsPathFlag) if err != nil { - log.Errorf("error loading config file at '%s', bailing", *configFlag) + log.Errorf("error loading config file '%s', bailing", *configFlag) return err }