Merge pull request #43 from gravitational/alexander/update

re-vendor configure and update code
This commit is contained in:
Alexander Klizhentas 2015-10-30 17:09:58 -07:00
commit 8df6d4e0f9
11 changed files with 1329 additions and 27 deletions

2
Godeps/Godeps.json generated
View file

@ -67,7 +67,7 @@
},
{
"ImportPath": "github.com/gravitational/configure",
"Rev": "725b3af8847feced5316be064a297d7c8a1052b3"
"Rev": "af81ee7530fe68dbac8824c44f9fd0b0546c3efb"
},
{
"ImportPath": "github.com/gravitational/form",

View file

@ -1,24 +1,43 @@
# Configure
Package configure generates configuration tools based on a struct
definition with tags. It can read a configuration for a struct
from YAML, environment variables and command line.
`configure` is a golang library that populates a struct from environment variables, command line arugments and YAML files.
It works by reading a struct definition with special tags.
```go
// Given the struct definition:
type Config struct {
StringVar string `env:"TEST_STRING_VAR" cli:"string" yaml:"string"`
BoolVar bool `env:"TEST_BOOL_VAR" cli:"bool" yaml:"bool"`
IntVar int `env:"TEST_INT_VAR" cli:"int" yaml:"int"`
HexVar hexType `env:"TEST_HEX_VAR" cli:"hex" yaml:"hex"`
MapVar map[string]string `env:"TEST_MAP_VAR" cli:"map" yaml:"map,flow"`
SliceMapVar []map[string]string `env:"TEST_SLICE_MAP_VAR" cli:"slice" yaml:"slice,flow"`
}
### Usage
The latest can be seen if you run
```
You can start initializing the struct from YAML, command line or environment.
```shell
# use godoc for more details
godoc github.com/gravitational/configure
```
But here's a quickstart: Define a sample structure, for examlpe:
```go
type Config struct {
StringVar string `env:"STRING_VAR" cli:"string-var" yaml:"string_var"`
BoolVar bool `env:"BOOL_VAR" cli:"bool_var" yaml:"bool_var"`
IntVar int `env:"INT_VAR" cli:"int_var" yaml:"int_var"`
HexVar hexType `env:"HEX_VAR" cli:"hex_var" yaml:"hex_var"`
MapVar map[string]string `env:"MAP_VAR" cli:"map_var" yaml:"map_var,flow"`
SliceMapVar []map[string]string `env:"SLICE_MAP_VAR" cli:"slice_var" yaml:"slice_var,flow"`
}
```
Then you can query the environment and populate that structure from environment variables, YAML files or command line arguments.
```go
import (
"os"
"github.com/gravitational/configure"
)
func main() {
var cfg Config
// parse environment variables
err := configure.ParseEnv(&cfg)
// parse YAML
err = configure.ParseYAML(&cfg)
// parse command line arguments
err = configure.ParseCommandLine(&cfg, os.Ars[1:])
}
```

View file

@ -6,6 +6,7 @@ import (
"strconv"
"strings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure/cstrings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
"github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/alecthomas/kingpin.v2"
)
@ -139,7 +140,7 @@ func setMap(kv *map[string]string, val string) error {
if len(*kv) == 0 {
*kv = make(map[string]string)
}
for _, i := range SplitComma(val) {
for _, i := range cstrings.SplitComma(val) {
vals := strings.SplitN(i, ":", 2)
if len(vals) != 2 {
return trace.Errorf("extra options should be defined like KEY:VAL")

View file

@ -1,4 +1,4 @@
package configure
package cstrings
import (
"bufio"
@ -29,6 +29,21 @@ func Split(delim, escape rune, v string) []string {
return out
}
// SplitAt splits array of strings at a given separator
func SplitAt(args []string, sep string) ([]string, []string) {
index := -1
for i, a := range args {
if a == sep {
index = i
break
}
}
if index == -1 {
return args, []string{}
}
return args[:index], args[index+1:]
}
type splitter struct {
delim rune
escape rune

View file

@ -1,9 +1,13 @@
package configure
package cstrings
import (
"testing"
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
)
func TestStrings(t *testing.T) { TestingT(t) }
type USuite struct {
}

View file

@ -6,7 +6,9 @@ import (
"fmt"
"strings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure/cstrings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
"github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/alecthomas/kingpin.v2"
)
// KeyVal is map that can parse itself from string, represented as a
@ -18,7 +20,7 @@ func (kv *KeyVal) Set(v string) error {
if len(*kv) == 0 {
*kv = make(map[string]string)
}
for _, i := range SplitComma(v) {
for _, i := range cstrings.SplitComma(v) {
vals := strings.SplitN(i, ":", 2)
if len(vals) != 2 {
return trace.Errorf("extra options should be defined like KEY:VAL")
@ -47,6 +49,14 @@ func (kv *KeyVal) String() string {
return b.String()
}
// KeyValParam accepts a kingpin setting parameter and returns
// kingpin-compatible value
func KeyValParam(s kingpin.Settings) *KeyVal {
kv := make(KeyVal)
s.SetValue(&kv)
return &kv
}
// KeyValSlice is a list of key value strings
type KeyValSlice []map[string]string

View file

@ -0,0 +1,96 @@
package schema
import (
"encoding/json"
"strings"
)
type configV1 struct {
Params []paramSpec `json:"params"`
}
type paramSpec struct {
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"`
Check string `json:"check"`
Default string `json:"default"`
CLI cliSpec `json:"cli"` // cli-specific settings
Env string `json:"env"` // environment variable name
Required bool `json:"required"`
S json.RawMessage `json:"spec"`
}
func (p *paramSpec) common() paramCommon {
return paramCommon{
name: p.Name,
descr: p.Description,
check: p.Check,
def: p.Default,
cli: p.CLI,
req: p.Required,
env: p.Env,
}
}
type paramCommon struct {
name string
descr string
check string
req bool
cli cliSpec
def string
env string
}
func (p *paramCommon) EnvName() string {
if p.env != "" {
return p.env
}
return strings.ToUpper(p.name)
}
func (p *paramCommon) CLIName() string {
if p.cli.Name != "" {
return p.cli.Name
}
return p.name
}
func (p *paramCommon) Name() string {
return p.name
}
func (p *paramCommon) Description() string {
return p.descr
}
func (p *paramCommon) Check() string {
return p.check
}
func (p *paramCommon) Required() bool {
return p.req
}
func (p *paramCommon) Default() string {
return p.def
}
type cliSpec struct {
Name string `json:"name"`
Type string `json:"type"` // type is either 'flag' or 'arg', 'flag is the default'
}
func (s *paramSpec) Spec() paramSpec {
return *s
}
type kvSpec struct {
Separator string `json:"separator"`
Keys []paramSpec `json:"keys"`
}
type enumSpec struct {
Values []string `json:"values"`
}

View file

@ -0,0 +1,669 @@
package schema
import (
"encoding/json"
"fmt"
"go/ast"
"go/parser"
"io"
"strconv"
"strings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure/cstrings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
"github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/alecthomas/kingpin.v2"
)
func ParseJSON(r io.Reader) (*Config, error) {
var c *configV1
if err := json.NewDecoder(r).Decode(&c); err != nil {
return nil, trace.Wrap(err)
}
return newParser().parse(*c)
}
func ParseVariablesJSON(r io.Reader) (*Config, error) {
var variables []paramSpec
if err := json.NewDecoder(r).Decode(&variables); err != nil {
return nil, trace.Wrap(err)
}
return newParser().parse(configV1{Params: variables})
}
type Config struct {
Params []Param
}
func (c *Config) Vars() map[string]string {
vars := make(map[string]string, len(c.Params))
for _, p := range c.Params {
k, v := p.Vars()
vars[k] = v
}
return vars
}
func (c *Config) EnvVars() map[string]string {
vars := make(map[string]string, len(c.Params))
for _, p := range c.Params {
k, v := p.EnvVars()
vars[k] = v
}
return vars
}
func (c *Config) Args() []string {
args := []string{}
for _, p := range c.Params {
args = append(args, p.Args()...)
}
return args
}
func (c *Config) ParseArgs(args []string) error {
app := cliApp(c, false)
_, err := app.Parse(args)
return err
}
func (c *Config) ParseEnv() error {
app := cliApp(c, true)
_, err := app.Parse([]string{})
return err
}
func (c *Config) ParseVars(vars map[string]string) error {
for _, p := range c.Params {
val, ok := vars[p.Name()]
if !ok {
if p.Required() {
return trace.Errorf("missing value for required variable: %v", p.Name())
} else {
val = p.Default()
}
}
if err := p.Set(val); err != nil {
return trace.Wrap(err)
}
}
return nil
}
func cliApp(c *Config, useEnv bool) *kingpin.Application {
app := kingpin.New("app", "Orbit package configuration tool")
for _, p := range c.Params {
cliFlag(app, p, useEnv)
}
return app
}
func cliFlag(app *kingpin.Application, p Param, useEnv bool) {
name := p.CLIName()
f := app.Flag(name, p.Description())
if p.Required() {
f = f.Required()
}
if p.Default() != "" {
f = f.Default(p.Default())
}
if useEnv {
f.OverrideDefaultFromEnvar(p.EnvName())
}
SetParam(p, f)
}
func SetParam(p Param, s kingpin.Settings) {
s.SetValue(p)
}
type Param interface {
Name() string
CLIName() string
Description() string
Check() string
Required() bool
Default() string
// New returns a new instance of the param identical to this
New() Param
// Set is required to set parameters from command line string
Set(string) error
// String is required to output value to command line string
String() string
// Args returns argument strings in cli format
Args() []string
// Values returns a tuple with environment variable name and value
EnvVars() (string, string)
// Vars returns a tuple with the variable name and value
Vars() (string, string)
EnvName() string
}
func newParser() *cparser {
return &cparser{
params: []Param{},
}
}
type cparser struct {
params []Param
}
func (p *cparser) parse(c configV1) (*Config, error) {
cfg := &Config{
Params: make([]Param, len(c.Params)),
}
// parse types
if len(c.Params) != 0 {
for i, ts := range c.Params {
pr, err := p.parseParam(ts, false)
if err != nil {
return nil, err
}
cfg.Params[i] = pr
}
}
return cfg, nil
}
func (p *cparser) parseParam(s paramSpec, scalar bool) (Param, error) {
if s.Name == "" {
return nil, trace.Errorf("set a type name")
}
if err := p.checkName(s.Name); err != nil {
return nil, err
}
if s.Type == "" {
return nil, trace.Errorf("set a type for '%v'", s.Name)
}
switch s.Type {
case "String":
pr := &StringParam{}
pr.paramCommon = s.common()
return pr, nil
case "Path":
pr := &PathParam{}
pr.paramCommon = s.common()
return pr, nil
case "Int":
pr := &IntParam{}
pr.paramCommon = s.common()
return pr, nil
case "Bool":
pr := &BoolParam{}
pr.paramCommon = s.common()
return pr, nil
case "KeyVal":
return p.parseKeyVal(s)
case "Enum":
return p.parseEnum(s)
case "List":
if scalar {
return nil, trace.Errorf(
"scalar values are not allowed here: '%v'", s.Type)
}
return p.parseList(s)
}
return nil, trace.Errorf("unrecognized type: '%v'", s.Type)
}
func (p *cparser) parseList(s paramSpec) (Param, error) {
var ps *paramSpec
if err := json.Unmarshal(s.S, &ps); err != nil {
return nil, trace.Wrap(err, "failed to parse: '%v'", string(s.S))
}
el, err := p.parseParam(*ps, false)
if err != nil {
return nil, err
}
l := &ListParam{el: el}
l.paramCommon = s.common()
return l, nil
}
func (p *cparser) parseEnum(s paramSpec) (Param, error) {
var e *enumSpec
if err := json.Unmarshal(s.S, &e); err != nil {
return nil, trace.Wrap(
err, fmt.Sprintf("failed to parse: '%v'", string(s.S)))
}
if len(e.Values) == 0 {
return nil, trace.Errorf("provide at least one value for '%v'", s.Name)
}
values := make([]string, len(e.Values))
seen := make(map[string]bool, len(e.Values))
for i, v := range e.Values {
if v == "" {
return nil, trace.Errorf("value can not be an empty string")
}
if seen[v] {
return nil, trace.Errorf("duplicate value: '%v'", v)
}
values[i] = v
}
ep := &EnumParam{values: values}
ep.paramCommon = s.common()
return ep, nil
}
func (p *cparser) parseKeyVal(s paramSpec) (Param, error) {
var k *kvSpec
if err := json.Unmarshal(s.S, &k); err != nil {
return nil, trace.Wrap(
err, fmt.Sprintf("failed to parse: '%v'", string(s.S)))
}
if len(k.Keys) == 0 {
return nil, trace.Errorf("provide at least one key for '%v'", s.Name)
}
keys := make([]Param, len(k.Keys))
for i, ks := range k.Keys {
k, err := p.parseParam(ks, true)
if err != nil {
return nil, err
}
keys[i] = k
}
if err := checkSameNames(keys); err != nil {
return nil, err
}
kv := &KVParam{keys: keys, separator: k.Separator}
kv.paramCommon = s.common()
return kv, nil
}
func (p *cparser) checkName(n string) error {
for _, pr := range p.params {
if pr.Name() == n {
return trace.Errorf("parameter '%v' is already defined", n)
}
}
e, err := parser.ParseExpr(n)
if err != nil {
return trace.Wrap(
err, fmt.Sprintf("failed to parse name: '%v'", n))
}
if _, ok := e.(*ast.Ident); !ok {
return trace.Wrap(
err, fmt.Sprintf("name should be a valid identifier: '%v'", n))
}
return nil
}
func checkSameNames(ps []Param) error {
n := map[string]bool{}
for _, p := range ps {
if n[p.Name()] {
return trace.Errorf("parameter '%v' is already defined", n)
}
n[p.Name()] = true
}
return nil
}
type PathParam struct {
paramCommon
val *string
}
func (p *PathParam) New() Param {
return &PathParam{p.paramCommon, nil}
}
func (p *PathParam) Args() []string {
return []string{fmt.Sprintf("--%v", p.CLIName()), p.String()}
}
func (p *PathParam) EnvVars() (string, string) {
return p.EnvName(), p.String()
}
func (p *PathParam) Vars() (string, string) {
return p.Name(), p.String()
}
func (p *PathParam) Set(s string) error {
p.val = &s
return nil
}
func (p *PathParam) String() string {
if p.val == nil {
return p.Default()
}
return *p.val
}
type StringParam struct {
paramCommon
val *string
}
func (p *StringParam) New() Param {
return &StringParam{p.paramCommon, nil}
}
func (p *StringParam) Set(s string) error {
p.val = &s
return nil
}
func (p *StringParam) String() string {
if p.val == nil {
return p.Default()
}
return *p.val
}
func (p *StringParam) Args() []string {
return []string{fmt.Sprintf("--%v", p.CLIName()), p.String()}
}
func (p *StringParam) EnvVars() (string, string) {
return p.EnvName(), p.String()
}
func (p *StringParam) Vars() (string, string) {
return p.Name(), p.String()
}
type BoolParam struct {
paramCommon
val *bool
}
func (p *BoolParam) New() Param {
return &BoolParam{p.paramCommon, nil}
}
func (p *BoolParam) Vars() (string, string) {
return p.Name(), p.String()
}
func (p *BoolParam) Set(s string) error {
v, err := strconv.ParseBool(s)
if err != nil {
return err
}
p.val = &v
return nil
}
func (p *BoolParam) String() string {
if p.val == nil {
return "false"
}
return fmt.Sprintf("%v", *p.val)
}
func (p *BoolParam) Args() []string {
return []string{fmt.Sprintf("--%v", p.CLIName()), p.String()}
}
func (p *BoolParam) EnvVars() (string, string) {
return p.EnvName(), p.String()
}
type IntParam struct {
paramCommon
val *int64
}
func (p *IntParam) Set(s string) error {
v, err := strconv.ParseInt(s, 0, 64)
if err != nil {
return err
}
p.val = &v
return nil
}
func (p *IntParam) String() string {
if p.val == nil {
return p.Default()
}
return fmt.Sprintf("%v", *p.val)
}
func (p *IntParam) New() Param {
return &IntParam{p.paramCommon, nil}
}
func (p *IntParam) Args() []string {
return []string{fmt.Sprintf("--%v", p.CLIName()), p.String()}
}
func (p *IntParam) EnvVars() (string, string) {
return p.EnvName(), p.String()
}
func (p *IntParam) Vars() (string, string) {
return p.Name(), p.String()
}
type ListParam struct {
paramCommon
el Param
values []Param
}
func (p *ListParam) CLIName() string {
return p.el.CLIName()
}
func (p *ListParam) EnvName() string {
return p.el.EnvName()
}
func (p *ListParam) Set(s string) error {
// this is to support setting from environment variables
values := cstrings.Split(',', '\\', s)
for _, v := range values {
el := p.el.New()
if err := el.Set(v); err != nil {
return err
}
p.values = append(p.values, el)
}
return nil
}
func (p *ListParam) New() Param {
return &ListParam{p.paramCommon, p.el, nil}
}
func (p *ListParam) String() string {
if len(p.values) == 0 {
return p.Default()
}
out := make([]string, len(p.values))
for i, v := range p.values {
out[i] = v.String()
}
return fmt.Sprintf("[%v]", strings.Join(out, ","))
}
func (p *ListParam) Args() []string {
if len(p.values) == 0 {
return []string{}
}
out := make([]string, 0, len(p.values))
for _, v := range p.values {
out = append(out, v.Args()...)
}
return out
}
func (p *ListParam) EnvVars() (string, string) {
if len(p.values) == 0 {
return p.EnvName(), p.Default()
}
out := make([]string, len(p.values))
for i, v := range p.values {
_, out[i] = v.EnvVars()
}
return p.el.EnvName(), strings.Join(out, ",")
}
func (p *ListParam) Vars() (string, string) {
if len(p.values) == 0 {
return p.Name(), p.Default()
}
out := make([]string, len(p.values))
for i, v := range p.values {
_, out[i] = v.EnvVars()
}
return p.Name(), strings.Join(out, ",")
}
type KVParam struct {
paramCommon
separator string
keys []Param
values []Param
}
func (p *KVParam) sep() string {
if p.separator == "" {
return ":"
}
return ""
}
func (p *KVParam) Set(s string) error {
sep := p.sep()
parts := strings.Split(s, sep)
if len(parts) != len(p.keys) {
return trace.Errorf(
"expected elements separated by '%v', got '%v'", sep, s)
}
values := make([]Param, len(p.keys))
for i, pt := range parts {
el := p.keys[i].New()
if err := el.Set(pt); err != nil {
return err
}
values[i] = el
}
p.values = values
return nil
}
func (p *KVParam) String() string {
if len(p.values) == 0 {
return p.Default()
}
out := make([]string, len(p.values))
for i, v := range p.values {
out[i] = v.String()
}
return fmt.Sprintf("{%v}", strings.Join(out, p.sep()))
}
func (p *KVParam) New() Param {
keys := make([]Param, len(p.keys))
for i, k := range p.keys {
keys[i] = k.New()
}
return &KVParam{p.paramCommon, p.separator, keys, nil}
}
func (p *KVParam) Args() []string {
if len(p.values) == 0 {
return []string{}
}
vals := make([]string, len(p.values))
for i, v := range p.values {
vals[i] = v.String()
}
return []string{
fmt.Sprintf("--%v", p.CLIName()), strings.Join(vals, p.sep())}
}
func (p *KVParam) EnvVars() (string, string) {
if len(p.values) == 0 {
return p.EnvName(), p.Default()
}
vals := make([]string, len(p.values))
for i, v := range p.values {
vals[i] = v.String()
}
return p.EnvName(), strings.Join(vals, p.sep())
}
func (p *KVParam) Vars() (string, string) {
if len(p.values) == 0 {
return p.Name(), p.Default()
}
vals := make([]string, len(p.values))
for i, v := range p.values {
vals[i] = v.String()
}
return p.Name(), strings.Join(vals, p.sep())
}
type EnumParam struct {
paramCommon
values []string
value *string
}
func (p *EnumParam) Set(s string) error {
found := false
for _, v := range p.values {
if s == v {
found = true
}
}
if !found {
return trace.Errorf(
"value '%v' is not one of the allowed '%v'",
s, strings.Join(p.values, ","),
)
}
p.value = &s
return nil
}
func (p *EnumParam) String() string {
if p.value == nil {
return p.Default()
}
return *p.value
}
func (p *EnumParam) New() Param {
return &EnumParam{p.paramCommon, p.values, nil}
}
func (p *EnumParam) Args() []string {
if p.value == nil {
return []string{}
}
return []string{fmt.Sprintf("--%v", p.CLIName()), *p.value}
}
func (p *EnumParam) EnvVars() (string, string) {
return p.EnvName(), p.String()
}
func (p *EnumParam) Vars() (string, string) {
return p.Name(), p.String()
}

View file

@ -0,0 +1,487 @@
package schema
import (
"fmt"
"os"
"strings"
"testing"
"github.com/kr/pretty"
"github.com/kylelemons/godebug/diff"
. "github.com/gravitational/teleport/Godeps/_workspace/src/gopkg.in/check.v1"
)
func TestConfig(t *testing.T) { TestingT(t) }
type ConfigSuite struct {
}
var _ = Suite(&ConfigSuite{})
func (s *ConfigSuite) TestParseTypes(c *C) {
tcs := []struct {
name string
cfg string
expect *Config
}{
{
name: "string param",
cfg: `{
"params": [
{
"name": "string1",
"type": "String"
}
]
}`,
expect: &Config{
Params: []Param{
&StringParam{paramCommon{name: "string1"}, nil},
},
},
},
{
name: "bool param",
cfg: `{
"params": [
{
"name": "bool1",
"type": "Bool"
}
]
}`,
expect: &Config{
Params: []Param{
&BoolParam{paramCommon{name: "bool1"}, nil},
},
},
},
{
name: "enum param",
cfg: `{
"params": [
{
"name": "enum1",
"type": "Enum",
"spec": {
"values": ["a", "b"]
}
}
]
}`,
expect: &Config{
Params: []Param{
&EnumParam{paramCommon{name: "enum1"}, []string{"a", "b"}, nil},
},
},
},
{
name: "key val param",
cfg: `{
"params": [
{
"name": "kv1",
"type": "KeyVal",
"default": "path1:hello",
"required": true,
"spec": {
"separator": ":",
"keys": [
{"type": "Path", "name": "path1"},
{"type": "Path", "name": "path2"}
]
}
}
]
}`,
expect: &Config{
Params: []Param{
&KVParam{
paramCommon{name: "kv1", req: true, def: "path1:hello"},
":",
[]Param{
&PathParam{paramCommon{name: "path1"}, nil},
&PathParam{paramCommon{name: "path2"}, nil},
},
nil,
},
},
},
},
{
name: "list key val param",
cfg: `{
"params": [
{
"name": "mounts",
"type": "List",
"spec": {
"name": "volume",
"type": "KeyVal",
"spec": {
"separator": ":",
"keys": [
{"type": "Path", "name": "path1"},
{"type": "Path", "name": "path2"}
]
}
}
}
]
}`,
expect: &Config{
Params: []Param{
&ListParam{
paramCommon{name: "mounts"},
&KVParam{
paramCommon{name: "volume"},
":",
[]Param{
&PathParam{paramCommon{name: "path1"}, nil},
&PathParam{paramCommon{name: "path2"}, nil},
},
nil,
},
nil,
},
},
},
},
}
for i, tc := range tcs {
comment := Commentf("test #%d (%v) cfg=%v, param=%v", i+1, tc.name, tc.cfg)
cfg, err := ParseJSON(strings.NewReader(tc.cfg))
c.Assert(err, IsNil, comment)
c.Assert(len(cfg.Params), Equals, len(tc.expect.Params))
for i, _ := range cfg.Params {
c.Assert(cfg.Params[i], DeepEquals, tc.expect.Params[i], comment)
}
}
}
func (s *ConfigSuite) TestArgs(c *C) {
tcs := []struct {
name string
cfg string
expect *Config
expectErr bool
args []string
vars map[string]string
}{
{
name: "string param",
cfg: `{
"params": [
{
"name": "string1",
"type": "String"
}
]
}`,
expect: &Config{
Params: []Param{
&StringParam{paramCommon{name: "string1"}, str("val1")},
},
},
args: []string{"--string1", "val1"},
vars: map[string]string{"STRING1": "val1"},
},
{
name: "check defaults",
cfg: `{
"params": [
{
"name": "string1",
"type": "String",
"default": "default value"
}
]
}`,
expect: &Config{
Params: []Param{
&StringParam{
paramCommon{name: "string1", def: "default value"},
str("default value")},
},
},
args: []string{},
vars: map[string]string{"STRING1": "default value"},
},
{
name: "check missing required param",
cfg: `{
"params": [
{
"name": "string1",
"type": "String",
"required": true
}
]
}`,
expectErr: true,
args: []string{},
},
{
name: "check key values",
cfg: `{
"params": [
{
"name": "volume",
"env": "PREFIX_VOLUME",
"type": "KeyVal",
"spec": {
"keys": [
{"name": "src", "type":"Path"},
{"name": "dst", "type":"Path"}
]
}
}
]
}`,
args: []string{"--volume", "/tmp/hello:/var/hello"},
vars: map[string]string{"PREFIX_VOLUME": "/tmp/hello:/var/hello"},
expect: &Config{
Params: []Param{
&KVParam{
paramCommon{name: "volume", env: "PREFIX_VOLUME"},
"",
[]Param{
&PathParam{paramCommon{name: "src"}, nil},
&PathParam{paramCommon{name: "dst"}, nil},
},
[]Param{
&PathParam{paramCommon{name: "src"}, str("/tmp/hello")},
&PathParam{paramCommon{name: "dst"}, str("/var/hello")},
},
},
},
},
},
{
name: "list of key values",
cfg: `{
"params": [
{
"type": "List",
"name": "mounts",
"spec": {
"name": "volume",
"type": "KeyVal",
"spec": {
"keys": [
{"name": "src", "type":"Path"},
{"name": "dst", "type":"Path"}
]
}
}
}
]
}`,
args: []string{"--volume", "/tmp/hello:/var/hello"},
vars: map[string]string{"VOLUME": "/tmp/hello:/var/hello"},
expect: &Config{
Params: []Param{
&ListParam{
paramCommon{name: "mounts"},
&KVParam{
paramCommon{name: "volume"},
"",
[]Param{
&PathParam{paramCommon{name: "src"}, nil},
&PathParam{paramCommon{name: "dst"}, nil},
},
nil,
},
[]Param{
&KVParam{
paramCommon{name: "volume"},
"",
[]Param{
&PathParam{paramCommon{name: "src"}, nil},
&PathParam{paramCommon{name: "dst"}, nil},
},
[]Param{
&PathParam{paramCommon{name: "src"}, str("/tmp/hello")},
&PathParam{paramCommon{name: "dst"}, str("/var/hello")},
},
},
},
},
},
},
},
}
for i, tc := range tcs {
comment := Commentf(
"test #%d (%v) cfg=%v, args=%v", i+1, tc.name, tc.cfg, tc.args)
cfg, err := ParseJSON(strings.NewReader(tc.cfg))
c.Assert(err, IsNil, comment)
if tc.expectErr {
c.Assert(cfg.ParseArgs(tc.args), NotNil)
continue
}
// make sure all the values have been parsed
c.Assert(cfg.ParseArgs(tc.args), IsNil)
c.Assert(len(cfg.Params), Equals, len(tc.expect.Params))
for i, _ := range cfg.Params {
comment := Commentf(
"test #%d (%v) cfg=%v, args=%v\n%v",
i+1, tc.name, tc.cfg, tc.args,
diff.Diff(
fmt.Sprintf("%# v", pretty.Formatter(cfg.Params[i])),
fmt.Sprintf("%# v", pretty.Formatter(tc.expect.Params[i]))),
)
c.Assert(cfg.Params[i], DeepEquals, tc.expect.Params[i], comment)
}
// make sure args are equivalent to the passed arguments
if len(tc.args) != 0 {
args := cfg.Args()
c.Assert(args, DeepEquals, tc.args, comment)
}
// make sure vars are what we expect them to be
if len(tc.vars) != 0 {
c.Assert(cfg.EnvVars(), DeepEquals, tc.vars, comment)
}
}
}
func (s *ConfigSuite) TestEnvVars(c *C) {
tcs := []struct {
name string
cfg string
expect map[string]string
}{
{
name: "string param",
cfg: `{
"params": [
{
"env": "ENV_STRING1",
"name": "string1",
"type": "String"
}
]
}`,
expect: map[string]string{"ENV_STRING1": "val1"},
},
{
name: "list of key values",
cfg: `{
"params": [
{
"type": "List",
"name": "mounts",
"spec": {
"name": "volume",
"env": "PREFIX_VOLUME",
"type": "KeyVal",
"spec": {
"keys": [
{"name": "src", "type":"Path"},
{"name": "dst", "type":"Path"}
]
}
}
}
]
}`,
expect: map[string]string{
"PREFIX_VOLUME": "/tmp/hello:/var/hello,/tmp/hello1:/var/hello2",
},
},
}
for i, tc := range tcs {
comment := Commentf(
"test #%d (%v) cfg=%v", i+1, tc.name, tc.cfg)
cfg, err := ParseJSON(strings.NewReader(tc.cfg))
c.Assert(err, IsNil, comment)
os.Clearenv()
for k, v := range tc.expect {
os.Setenv(k, v)
}
c.Assert(cfg.ParseEnv(), IsNil)
c.Assert(cfg.EnvVars(), DeepEquals, tc.expect, comment)
}
}
func (s *ConfigSuite) TestParseVars(c *C) {
tcs := []struct {
name string
cfg string
expect map[string]string
}{
{
name: "string param",
cfg: `{
"params": [
{
"env": "ENV_STRING1",
"name": "string1",
"type": "String"
}
]
}`,
expect: map[string]string{"string1": "val1"},
},
{
name: "int param default",
cfg: `{
"params": [
{
"name": "int1",
"type": "Int",
"default": "-1"
}
]
}`,
expect: map[string]string{"int1": "-1"},
},
{
name: "list of key values",
cfg: `{
"params": [
{
"type": "List",
"name": "mounts",
"spec": {
"name": "volume",
"type": "KeyVal",
"spec": {
"keys": [
{"name": "src", "type":"Path"},
{"name": "dst", "type":"Path"}
]
}
}
}
]
}`,
expect: map[string]string{
"mounts": "/tmp/hello:/var/hello,/tmp/hello1:/var/hello2",
},
},
}
for i, tc := range tcs {
comment := Commentf(
"test #%d (%v) cfg=%v", i+1, tc.name, tc.cfg)
cfg, err := ParseJSON(strings.NewReader(tc.cfg))
c.Assert(err, IsNil, comment)
vars := make(map[string]string)
for k, v := range tc.expect {
vars[k] = v
}
c.Assert(cfg.ParseVars(vars), IsNil)
c.Assert(cfg.Vars(), DeepEquals, tc.expect, comment)
}
}
func str(val string) *string {
return &val
}

View file

@ -10,6 +10,7 @@ import (
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure/cstrings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
)
@ -169,7 +170,7 @@ type ReverseTunnelConfig struct {
type NetAddrSlice []utils.NetAddr
func (s *NetAddrSlice) Set(val string) error {
values := configure.SplitComma(val)
values := cstrings.SplitComma(val)
out := make([]utils.NetAddr, len(values))
for i, v := range values {
a, err := utils.ParseAddr(v)
@ -189,7 +190,7 @@ func (kv *KeyVal) Set(v string) error {
if len(*kv) == 0 {
*kv = make(map[string]string)
}
for _, i := range configure.SplitComma(v) {
for _, i := range cstrings.SplitComma(v) {
vals := strings.SplitN(i, ":", 2)
if len(vals) != 2 {
return trace.Errorf("extra options should be defined like KEY:VAL")

View file

@ -8,7 +8,7 @@ import (
"strings"
"text/template"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/configure/cstrings"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/trace"
)
@ -60,7 +60,7 @@ func (c *ctx) Env(key string) (string, error) {
if !ok {
return "", trace.Errorf("environment variable '%v' is not set", key)
}
values := configure.SplitComma(v)
values := cstrings.SplitComma(v)
out := make([]string, len(values))
for i, p := range values {
out[i] = quoteYAML(p)