config: change valid tag to use regexp

A regular expression is much more useful than a list of strings.
This commit is contained in:
Alex Crawford 2014-12-20 23:26:05 -08:00
parent 40d943fb7a
commit af8e590575
9 changed files with 186 additions and 21 deletions

View File

@ -19,6 +19,7 @@ package config
import ( import (
"fmt" "fmt"
"reflect" "reflect"
"regexp"
"strings" "strings"
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/coreos/yaml" "github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/coreos/yaml"
@ -91,7 +92,7 @@ func IsZero(c interface{}) bool {
type ErrorValid struct { type ErrorValid struct {
Value string Value string
Valid []string Valid string
Field string Field string
} }
@ -125,16 +126,15 @@ func AssertValid(value reflect.Value, valid string) *ErrorValid {
if valid == "" || isZero(value) { if valid == "" || isZero(value) {
return nil return nil
} }
vs := fmt.Sprintf("%v", value.Interface()) vs := fmt.Sprintf("%v", value.Interface())
valids := strings.Split(valid, ",") if m, _ := regexp.MatchString(valid, vs); m {
for _, valid := range valids {
if vs == valid {
return nil return nil
} }
}
return &ErrorValid{ return &ErrorValid{
Value: vs, Value: vs,
Valid: valids, Valid: valid,
} }
} }

View File

@ -18,6 +18,7 @@ package config
import ( import (
"reflect" "reflect"
"regexp"
"strings" "strings"
"testing" "testing"
) )
@ -88,29 +89,29 @@ func TestAssertStructValid(t *testing.T) {
}{ }{
{struct{}{}, nil}, {struct{}{}, nil},
{struct { {struct {
A, b string `valid:"1,2"` A, b string `valid:"^1|2$"`
}{}, nil}, }{}, nil},
{struct { {struct {
A, b string `valid:"1,2"` A, b string `valid:"^1|2$"`
}{A: "1", b: "2"}, nil}, }{A: "1", b: "2"}, nil},
{struct { {struct {
A, b string `valid:"1,2"` A, b string `valid:"^1|2$"`
}{A: "1", b: "hello"}, nil}, }{A: "1", b: "hello"}, nil},
{struct { {struct {
A, b string `valid:"1,2"` A, b string `valid:"^1|2$"`
}{A: "hello", b: "2"}, &ErrorValid{Value: "hello", Field: "A", Valid: []string{"1", "2"}}}, }{A: "hello", b: "2"}, &ErrorValid{Value: "hello", Field: "A", Valid: "^1|2$"}},
{struct { {struct {
A, b int `valid:"1,2"` A, b int `valid:"^1|2$"`
}{}, nil}, }{}, nil},
{struct { {struct {
A, b int `valid:"1,2"` A, b int `valid:"^1|2$"`
}{A: 1, b: 2}, nil}, }{A: 1, b: 2}, nil},
{struct { {struct {
A, b int `valid:"1,2"` A, b int `valid:"^1|2$"`
}{A: 1, b: 9}, nil}, }{A: 1, b: 9}, nil},
{struct { {struct {
A, b int `valid:"1,2"` A, b int `valid:"^1|2$"`
}{A: 9, b: 2}, &ErrorValid{Value: "9", Field: "A", Valid: []string{"1", "2"}}}, }{A: 9, b: 2}, &ErrorValid{Value: "9", Field: "A", Valid: "^1|2$"}},
} }
for _, tt := range tests { for _, tt := range tests {
@ -120,6 +121,33 @@ func TestAssertStructValid(t *testing.T) {
} }
} }
func TestConfigCompile(t *testing.T) {
tests := []interface{}{
Etcd{},
File{},
Flannel{},
Fleet{},
Locksmith{},
OEM{},
Unit{},
Update{},
}
for _, tt := range tests {
ttt := reflect.TypeOf(tt)
for i := 0; i < ttt.NumField(); i++ {
ft := ttt.Field(i)
if !isFieldExported(ft) {
continue
}
if _, err := regexp.Compile(ft.Tag.Get("valid")); err != nil {
t.Errorf("bad regexp(%s.%s): want %v, got %s", ttt.Name(), ft.Name, nil, err)
}
}
}
}
func TestCloudConfigUnknownKeys(t *testing.T) { func TestCloudConfigUnknownKeys(t *testing.T) {
contents := ` contents := `
coreos: coreos:

View File

@ -17,7 +17,7 @@
package config package config
type File struct { type File struct {
Encoding string `yaml:"encoding" valid:"base64,b64,gz,gzip,gz+base64,gzip+base64,gz+b64,gzip+b64"` Encoding string `yaml:"encoding" valid:"^(base64|b64|gz|gzip|gz\\+base64|gzip\\+base64|gz\\+b64|gzip\\+b64)$"`
Content string `yaml:"content"` Content string `yaml:"content"`
Owner string `yaml:"owner"` Owner string `yaml:"owner"`
Path string `yaml:"path"` Path string `yaml:"path"`

48
config/file_test.go Normal file
View File

@ -0,0 +1,48 @@
/*
Copyright 2014 CoreOS, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"testing"
)
func TestEncodingValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "base64", isValid: true},
{value: "b64", isValid: true},
{value: "gz", isValid: true},
{value: "gzip", isValid: true},
{value: "gz+base64", isValid: true},
{value: "gzip+base64", isValid: true},
{value: "gz+b64", isValid: true},
{value: "gzip+b64", isValid: true},
{value: "gzzzzbase64", isValid: false},
{value: "gzipppbase64", isValid: false},
{value: "unknown", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(File{Encoding: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

View File

@ -22,7 +22,7 @@ type Unit struct {
Enable bool `yaml:"enable"` Enable bool `yaml:"enable"`
Runtime bool `yaml:"runtime"` Runtime bool `yaml:"runtime"`
Content string `yaml:"content"` Content string `yaml:"content"`
Command string `yaml:"command" valid:"start,stop,restart,reload,try-restart,reload-or-restart,reload-or-try-restart"` Command string `yaml:"command" valid:"^(start|stop|restart|reload|try-restart|reload-or-restart|reload-or-try-restart)$"`
DropIns []UnitDropIn `yaml:"drop_ins"` DropIns []UnitDropIn `yaml:"drop_ins"`
} }

46
config/unit_test.go Normal file
View File

@ -0,0 +1,46 @@
/*
Copyright 2014 CoreOS, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"testing"
)
func TestCommandValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "start", isValid: true},
{value: "stop", isValid: true},
{value: "restart", isValid: true},
{value: "reload", isValid: true},
{value: "try-restart", isValid: true},
{value: "reload-or-restart", isValid: true},
{value: "reload-or-try-restart", isValid: true},
{value: "tryrestart", isValid: false},
{value: "unknown", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Unit{Command: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

View File

@ -17,7 +17,7 @@
package config package config
type Update struct { type Update struct {
RebootStrategy string `yaml:"reboot_strategy" env:"REBOOT_STRATEGY" valid:"best-effort,etcd-lock,reboot,off"` RebootStrategy string `yaml:"reboot_strategy" env:"REBOOT_STRATEGY" valid:"^(best-effort|etcd-lock|reboot|off)$"`
Group string `yaml:"group" env:"GROUP"` Group string `yaml:"group" env:"GROUP"`
Server string `yaml:"server" env:"SERVER"` Server string `yaml:"server" env:"SERVER"`
} }

43
config/update_test.go Normal file
View File

@ -0,0 +1,43 @@
/*
Copyright 2014 CoreOS, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package config
import (
"testing"
)
func TestRebootStrategyValid(t *testing.T) {
tests := []struct {
value string
isValid bool
}{
{value: "best-effort", isValid: true},
{value: "etcd-lock", isValid: true},
{value: "reboot", isValid: true},
{value: "off", isValid: true},
{value: "besteffort", isValid: false},
{value: "unknown", isValid: false},
}
for _, tt := range tests {
isValid := (nil == AssertStructValid(Update{RebootStrategy: tt.value}))
if tt.isValid != isValid {
t.Errorf("bad assert (%s): want %t, got %t", tt.value, tt.isValid, isValid)
}
}
}

View File

@ -100,7 +100,7 @@ func TestUpdateFile(t *testing.T) {
}, },
{ {
config: config.Update{RebootStrategy: "wizzlewazzle"}, config: config.Update{RebootStrategy: "wizzlewazzle"},
err: &config.ErrorValid{Value: "wizzlewazzle", Field: "RebootStrategy", Valid: []string{"best-effort", "etcd-lock", "reboot", "off"}}, err: &config.ErrorValid{Value: "wizzlewazzle", Field: "RebootStrategy", Valid: "^(best-effort|etcd-lock|reboot|off)$"},
}, },
{ {
config: config.Update{Group: "master", Server: "http://foo.com"}, config: config.Update{Group: "master", Server: "http://foo.com"},