Skip to content

Commit

Permalink
config: add yaml flag (zalando#3069)
Browse files Browse the repository at this point in the history
YAML flag parses (preferably flow-style) YAML value from the command line
or unmarshals object yaml value from the yaml config file.

Example value:
```
bin/skipper -foo-flag='{foo: hello, bar: [world, "1"], baz: 2, qux: {baz: 3}}'
```

and equivalent branch in config yaml:
```yaml
foo-flag:
  foo: hello
  bar:
    - world
    - "1"
  baz: 2
  qux:
    baz: 3
```

This will be useful for zalando#2104

It is also a better alternative to manual parsing of micro-syntax like
e.g. implemented in zalando#2888

Signed-off-by: Alexander Yastrebov <[email protected]>
  • Loading branch information
AlexanderYastrebov authored and Janardhan Sharma committed Jul 19, 2024
1 parent 8cc6aab commit a956aa9
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 0 deletions.
42 changes: 42 additions & 0 deletions config/yamlflag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package config

import (
"fmt"

"gopkg.in/yaml.v2"
)

type yamlFlag[T any] struct {
Ptr **T
value string // only for Set
}

func newYamlFlag[T any](ptr **T) *yamlFlag[T] {
return &yamlFlag[T]{Ptr: ptr}
}

func (yf *yamlFlag[T]) Set(value string) error {
var opts T
if err := yaml.Unmarshal([]byte(value), &opts); err != nil {
return fmt.Errorf("failed to parse yaml: %w", err)
}
*yf.Ptr = &opts
yf.value = value
return nil
}

func (yf *yamlFlag[T]) UnmarshalYAML(unmarshal func(interface{}) error) error {
var opts T
if err := unmarshal(&opts); err != nil {
return err
}
*yf.Ptr = &opts
return nil
}

func (yf *yamlFlag[T]) String() string {
if yf == nil {
return ""
}
return yf.value
}
108 changes: 108 additions & 0 deletions config/yamlflag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package config

import (
"testing"

"gopkg.in/yaml.v2"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type yamlFlagTestConfig struct {
Foo string
Bar []string
Baz int
Qux *yamlFlagTestConfig
}

func TestYamlFlag(t *testing.T) {
t.Run("set", func(t *testing.T) {
cfg := struct {
Field *yamlFlagTestConfig
}{}

f := newYamlFlag(&cfg.Field)
v := `{foo: hello, bar: [world, "1"], baz: 2, qux: {baz: 3}}`
err := f.Set(v)

require.NoError(t, err)
assert.Equal(t, "hello", cfg.Field.Foo)
assert.Equal(t, []string{"world", "1"}, cfg.Field.Bar)
assert.Equal(t, 2, cfg.Field.Baz)
assert.Equal(t, 3, cfg.Field.Qux.Baz)

assert.Equal(t, v, f.String())
})

t.Run("set empty", func(t *testing.T) {
cfg := struct {
Field *yamlFlagTestConfig
}{}

f := newYamlFlag(&cfg.Field)
err := f.Set("")

require.NoError(t, err)
assert.Equal(t, &yamlFlagTestConfig{}, cfg.Field)
assert.Equal(t, "", f.String())
})

t.Run("set empty object", func(t *testing.T) {
cfg := struct {
Field *yamlFlagTestConfig
}{}

f := newYamlFlag(&cfg.Field)
err := f.Set("{}")

require.NoError(t, err)
assert.Equal(t, &yamlFlagTestConfig{}, cfg.Field)
assert.Equal(t, "{}", f.String())
})

t.Run("set invalid yaml", func(t *testing.T) {
cfg := struct {
Field *yamlFlagTestConfig
}{}

f := newYamlFlag(&cfg.Field)
v := `This is not a valid YAML`
err := f.Set(v)

assert.Error(t, err)
})

t.Run("unmarshal YAML", func(t *testing.T) {
cfg := struct {
Field *yamlFlagTestConfig
}{}

err := yaml.Unmarshal([]byte(`
field:
foo: hello
bar:
- world
- "1"
baz: 2
qux:
baz: 3
`), &cfg)

require.NoError(t, err)
assert.Equal(t, "hello", cfg.Field.Foo)
assert.Equal(t, []string{"world", "1"}, cfg.Field.Bar)
assert.Equal(t, 2, cfg.Field.Baz)
assert.Equal(t, 3, cfg.Field.Qux.Baz)
})

t.Run("unmarshal invalid YAML", func(t *testing.T) {
cfg := struct {
Field *yamlFlagTestConfig
}{}

err := yaml.Unmarshal([]byte(`This is not a valid YAML`), &cfg)

assert.Error(t, err)
})
}

0 comments on commit a956aa9

Please sign in to comment.