Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions pkg/config/binder/binder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Package binder binds OpenCloud yaml config files to config structs, as a leaf
// package that avoids importing the aggregate service config in pkg/config.
package binder

import (
"io/fs"
"os"
"path"
"strings"

gofig "github.com/gookit/config/v2"
gooyaml "github.com/gookit/config/v2/yaml"
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
)

// decoderConfigTagName sets the tag name to be used from the config structs
// currently we only support "yaml" because we only support config loading
// from yaml files and the yaml parser has no simple way to set a custom tag name to use
const decoderConfigTagName = "yaml"

// BindSourcesToStructs assigns any config value from a config file / env variable to struct `dst`.
func BindSourcesToStructs(service string, dst any) error {
fileSystem := os.DirFS("/")
filePath := strings.TrimLeft(path.Join(defaults.BaseConfigPath(), service+".yaml"), "/")
return BindSourcesToStructsFS(fileSystem, filePath, service, dst)
}

// BindSourcesToStructsFS is like BindSourcesToStructs but reads from the given fs.FS and path.
func BindSourcesToStructsFS(fileSystem fs.FS, filePath, service string, dst any) error {
cnf := gofig.NewWithOptions(service)
cnf.WithOptions(func(options *gofig.Options) {
options.ParseEnv = true
options.DecoderConfig.TagName = decoderConfigTagName
})
cnf.AddDriver(gooyaml.Driver)

yamlContent, err := fs.ReadFile(fileSystem, filePath)
if err != nil {
if os.IsNotExist(err) {
return nil
}

return err
}
// the error is ignored on purpose, matching the pre-extraction behavior:
// an unparseable yaml file binds nothing instead of failing the startup
_ = cnf.LoadSources("yaml", yamlContent)

err = cnf.BindStruct("", &dst)
if err != nil {
return err
}

return nil
}
55 changes: 55 additions & 0 deletions pkg/config/binder/binder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package binder

import (
"testing"
"testing/fstest"

"gotest.tools/v3/assert"
)

type TestConfig struct {
A string `yaml:"a"`
B string `yaml:"b"`
C string `yaml:"c"`
}

func TestBindSourcesToStructs(t *testing.T) {
// setup test env: one var set to pin env expansion, two deliberately
// unset to pin the defaults
t.Setenv("BINDER_TEST_SET_VAR", "from-env")
yaml := `
a: "${BINDER_TEST_SET_VAR|no-foo}"
b: "${BINDER_TEST_UNSET_VAR|no-bar}"
c: "${BINDER_TEST_OTHER_UNSET_VAR|code}"
`
filePath := "etc/opencloud/foo.yaml"
fs := fstest.MapFS{
filePath: {Data: []byte(yaml)},
}
// perform test
c := TestConfig{}
err := BindSourcesToStructsFS(fs, filePath, "foo", &c)
if err != nil {
t.Error(err)
}

assert.Equal(t, c.A, "from-env")
assert.Equal(t, c.B, "no-bar")
assert.Equal(t, c.C, "code")
}

func TestBindSourcesToStructs_UnknownFile(t *testing.T) {
// setup test env
filePath := "etc/opencloud/foo.yaml"
fs := fstest.MapFS{}
// perform test
c := TestConfig{}
err := BindSourcesToStructsFS(fs, filePath, "foo", &c)
if err != nil {
t.Error(err)
}

assert.Equal(t, c.A, "")
assert.Equal(t, c.B, "")
assert.Equal(t, c.C, "")
}
45 changes: 3 additions & 42 deletions pkg/config/helpers.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,15 @@
package config

import (
"io/fs"
"os"
"path"
"strings"

gofig "github.com/gookit/config/v2"
gooyaml "github.com/gookit/config/v2/yaml"
"github.com/opencloud-eu/opencloud/pkg/config/defaults"
)

var (
// decoderConfigTagName sets the tag name to be used from the config structs
// currently we only support "yaml" because we only support config loading
// from yaml files and the yaml parser has no simple way to set a custom tag name to use
decoderConfigTagName = "yaml"
"github.com/opencloud-eu/opencloud/pkg/config/binder"
)

// BindSourcesToStructs assigns any config value from a config file / env variable to struct `dst`.
// The implementation lives in pkg/config/binder.
func BindSourcesToStructs(service string, dst any) error {
fileSystem := os.DirFS("/")
filePath := strings.TrimLeft(path.Join(defaults.BaseConfigPath(), service+".yaml"), "/")
return bindSourcesToStructs(fileSystem, filePath, service, dst)
}

func bindSourcesToStructs(fileSystem fs.FS, filePath, service string, dst any) error {
cnf := gofig.NewWithOptions(service)
cnf.WithOptions(func(options *gofig.Options) {
options.ParseEnv = true
options.DecoderConfig.TagName = decoderConfigTagName
})
cnf.AddDriver(gooyaml.Driver)

yamlContent, err := fs.ReadFile(fileSystem, filePath)
if err != nil {
if os.IsNotExist(err) {
return nil
}

return err
}
_ = cnf.LoadSources("yaml", yamlContent)

err = cnf.BindStruct("", &dst)
if err != nil {
return err
}

return nil
return binder.BindSourcesToStructs(service, dst)
}

// LocalEndpoint returns the local endpoint for a given protocol and address.
Expand Down
48 changes: 2 additions & 46 deletions pkg/config/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,54 +4,10 @@ import (
"testing"
"testing/fstest"

"github.com/opencloud-eu/opencloud/pkg/config/binder"
"gotest.tools/v3/assert"
)

type TestConfig struct {
A string `yaml:"a"`
B string `yaml:"b"`
C string `yaml:"c"`
}

func TestBindSourcesToStructs(t *testing.T) {
// setup test env
yaml := `
a: "${FOO_VAR|no-foo}"
b: "${BAR_VAR|no-bar}"
c: "${CODE_VAR|code}"
`
filePath := "etc/opencloud/foo.yaml"
fs := fstest.MapFS{
filePath: {Data: []byte(yaml)},
}
// perform test
c := TestConfig{}
err := bindSourcesToStructs(fs, filePath, "foo", &c)
if err != nil {
t.Error(err)
}

assert.Equal(t, c.A, "no-foo")
assert.Equal(t, c.B, "no-bar")
assert.Equal(t, c.C, "code")
}

func TestBindSourcesToStructs_UnknownFile(t *testing.T) {
// setup test env
filePath := "etc/opencloud/foo.yaml"
fs := fstest.MapFS{}
// perform test
c := TestConfig{}
err := bindSourcesToStructs(fs, filePath, "foo", &c)
if err != nil {
t.Error(err)
}

assert.Equal(t, c.A, "")
assert.Equal(t, c.B, "")
assert.Equal(t, c.C, "")
}

func TestBindSourcesToStructs_NoEnvVar(t *testing.T) {
// setup test env
yaml := `
Expand Down Expand Up @@ -180,7 +136,7 @@ clientlog:
}
// perform test
c := Config{}
err := bindSourcesToStructs(fs, filePath, "foo", &c)
err := binder.BindSourcesToStructsFS(fs, filePath, "foo", &c)
if err != nil {
t.Error(err)
}
Expand Down