Compare commits
33 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
fb6f52b360 | ||
|
786cd2a539 | ||
|
45793f1254 | ||
|
b621756d92 | ||
|
a5b5c700a6 | ||
|
d7602f3c08 | ||
|
a20addd05e | ||
|
d9d89a6fa0 | ||
|
3c26376326 | ||
|
d3294bcb86 | ||
|
dda314b518 | ||
|
055a3c339a | ||
|
51f37100a1 | ||
|
88e8265cd6 | ||
|
6e2db882e6 | ||
|
3e2823df1b | ||
|
46cb51cf91 | ||
|
1a6cee5305 | ||
|
d02aa18839 | ||
|
e9bda98b54 | ||
|
badc874b74 | ||
|
c9e8c887b8 | ||
|
8be307de49 | ||
|
562c474275 | ||
|
5c5834863b | ||
|
44f0a949c5 | ||
|
106c4e7a2c | ||
|
6c1ba590aa | ||
|
45da664c59 | ||
|
2a71551ef2 | ||
|
84e1cb3242 | ||
|
5214ead926 | ||
|
e2c24c4cef |
@@ -5,6 +5,7 @@ go:
|
|||||||
|
|
||||||
install:
|
install:
|
||||||
- go get code.google.com/p/go.tools/cmd/cover
|
- go get code.google.com/p/go.tools/cmd/cover
|
||||||
|
- go get code.google.com/p/go.tools/cmd/vet
|
||||||
|
|
||||||
script:
|
script:
|
||||||
- ./test
|
- ./test
|
||||||
|
@@ -97,6 +97,29 @@ For more information on fleet configuration, see the [fleet documentation][fleet
|
|||||||
|
|
||||||
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md#configuration
|
[fleet-config]: https://github.com/coreos/fleet/blob/master/Documentation/deployment-and-configuration.md#configuration
|
||||||
|
|
||||||
|
#### flannel
|
||||||
|
|
||||||
|
The `coreos.flannel.*` parameters also work very similarly to `coreos.etcd.*` and `coreos.fleet.*`. They can be used to set enviornment variables for flanneld. Given the following cloud-config...
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
#cloud-config
|
||||||
|
|
||||||
|
coreos:
|
||||||
|
flannel:
|
||||||
|
etcd-prefix: /coreos.com/network2
|
||||||
|
```
|
||||||
|
|
||||||
|
...will generate systemd unit drop-in like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
[Service]
|
||||||
|
Environment="FLANNELD_ETCD_PREFIX=/coreos.com/network2"
|
||||||
|
```
|
||||||
|
|
||||||
|
For complete list of flannel configuraion parameters, see the [flannel documentation][flannel-readme].
|
||||||
|
|
||||||
|
[flannel-readme]: https://github.com/coreos/flannel/blob/master/README.md
|
||||||
|
|
||||||
#### update
|
#### update
|
||||||
|
|
||||||
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
The `coreos.update.*` parameters manipulate settings related to how CoreOS instances are updated.
|
||||||
|
34
Godeps/Godeps.json
generated
Normal file
34
Godeps/Godeps.json
generated
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"ImportPath": "github.com/coreos/coreos-cloudinit",
|
||||||
|
"GoVersion": "go1.3.1",
|
||||||
|
"Packages": [
|
||||||
|
"./..."
|
||||||
|
],
|
||||||
|
"Deps": [
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/cloudsigma/cepgo",
|
||||||
|
"Rev": "1bfc4895bf5c4d3b599f3f6ee142299488c8739b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/coreos/go-systemd/dbus",
|
||||||
|
"Rev": "4fbc5060a317b142e6c7bfbedb65596d5f0ab99b"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/dotcloud/docker/pkg/netlink",
|
||||||
|
"Comment": "v0.11.1-359-g55d41c3e21e1",
|
||||||
|
"Rev": "55d41c3e21e1593b944c06196ffb2ac57ab7f653"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/guelfey/go.dbus",
|
||||||
|
"Rev": "f6a3a2366cc39b8479cadc499d3c735fb10fbdda"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "github.com/tarm/goserial",
|
||||||
|
"Rev": "cdabc8d44e8e84f58f18074ae44337e1f2f375b9"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ImportPath": "gopkg.in/yaml.v1",
|
||||||
|
"Rev": "feb4ca79644e8e7e39c06095246ee54b1282c118"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
5
Godeps/Readme
generated
Normal file
5
Godeps/Readme
generated
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
This directory tree is generated automatically by godep.
|
||||||
|
|
||||||
|
Please do not edit.
|
||||||
|
|
||||||
|
See https://github.com/tools/godep for more information.
|
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
2
Godeps/_workspace/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/pkg
|
||||||
|
/bin
|
23
Godeps/_workspace/src/github.com/cloudsigma/cepgo/.gitignore
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/cloudsigma/cepgo/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
@@ -48,7 +48,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/tarm/goserial"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/tarm/goserial"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
@@ -23,7 +23,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const signalBuffer = 100
|
const signalBuffer = 100
|
@@ -18,7 +18,7 @@ package dbus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Conn) initJobs() {
|
func (c *Conn) initJobs() {
|
||||||
@@ -208,7 +208,7 @@ func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Proper
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
|
func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) {
|
||||||
return c.getProperty(unit, "org.freedesktop.systemd1." + unitType, propertyName)
|
return c.getProperty(unit, "org.freedesktop.systemd1."+unitType, propertyName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListUnits returns an array with all currently loaded units. Note that
|
// ListUnits returns an array with all currently loaded units. Note that
|
@@ -18,7 +18,7 @@ package dbus
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
@@ -17,7 +17,7 @@ limitations under the License.
|
|||||||
package dbus
|
package dbus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
// From the systemd docs:
|
// From the systemd docs:
|
@@ -20,7 +20,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -101,7 +101,7 @@ func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitSt
|
|||||||
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
|
// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer
|
||||||
// size of the channels, the comparison function for detecting changes and a filter
|
// size of the channels, the comparison function for detecting changes and a filter
|
||||||
// function for cutting down on the noise that your channel receives.
|
// function for cutting down on the noise that your channel receives.
|
||||||
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func (string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
|
func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) {
|
||||||
old := make(map[string]*UnitStatus)
|
old := make(map[string]*UnitStatus)
|
||||||
statusChan := make(chan map[string]*UnitStatus, buffer)
|
statusChan := make(chan map[string]*UnitStatus, buffer)
|
||||||
errChan := make(chan error, buffer)
|
errChan := make(chan error, buffer)
|
@@ -2,7 +2,7 @@ package introspect
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@@ -2,7 +2,7 @@ package introspect
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"reflect"
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
@@ -3,8 +3,8 @@
|
|||||||
package prop
|
package prop
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus"
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/github.com/guelfey/go.dbus/introspect"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/github.com/guelfey/go.dbus/introspect"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
61
Godeps/_workspace/src/github.com/tarm/goserial/basic_test.go
generated
vendored
Normal file
61
Godeps/_workspace/src/github.com/tarm/goserial/basic_test.go
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
package serial
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConnection(t *testing.T) {
|
||||||
|
c0 := &Config{Name: "/dev/ttyUSB0", Baud: 115200}
|
||||||
|
c1 := &Config{Name: "/dev/ttyUSB1", Baud: 115200}
|
||||||
|
|
||||||
|
s1, err := OpenPort(c0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s2, err := OpenPort(c1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan int, 1)
|
||||||
|
go func() {
|
||||||
|
buf := make([]byte, 128)
|
||||||
|
var readCount int
|
||||||
|
for {
|
||||||
|
n, err := s2.Read(buf)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
readCount++
|
||||||
|
t.Logf("Read %v %v bytes: % 02x %s", readCount, n, buf[:n], buf[:n])
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
ch <- readCount
|
||||||
|
close(ch)
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if _, err = s1.Write([]byte("hello")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, err = s1.Write([]byte(" ")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second)
|
||||||
|
if _, err = s1.Write([]byte("world")); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
time.Sleep(time.Second / 10)
|
||||||
|
|
||||||
|
ch <- 0
|
||||||
|
s1.Write([]byte(" ")) // We could be blocked in the read without this
|
||||||
|
c := <-ch
|
||||||
|
exp := 5
|
||||||
|
if c >= exp {
|
||||||
|
t.Fatalf("Expected less than %v read, got %v", exp, c)
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,7 @@ package yaml_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
"gopkg.in/yaml.v1"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/gopkg.in/yaml.v1"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
@@ -2,7 +2,7 @@ package yaml_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"gopkg.in/yaml.v1"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/gopkg.in/yaml.v1"
|
||||||
. "gopkg.in/check.v1"
|
. "gopkg.in/check.v1"
|
||||||
"math"
|
"math"
|
||||||
"strconv"
|
"strconv"
|
180
config/config.go
180
config/config.go
@@ -1,12 +1,27 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/coreos/coreos-cloudinit/third_party/gopkg.in/yaml.v1"
|
"github.com/coreos/coreos-cloudinit/Godeps/_workspace/src/gopkg.in/yaml.v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CloudConfig encapsulates the entire cloud-config configuration file and maps
|
// CloudConfig encapsulates the entire cloud-config configuration file and maps
|
||||||
@@ -16,6 +31,7 @@ type CloudConfig struct {
|
|||||||
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||||
Coreos struct {
|
Coreos struct {
|
||||||
Etcd Etcd `yaml:"etcd"`
|
Etcd Etcd `yaml:"etcd"`
|
||||||
|
Flannel Flannel `yaml:"flannel"`
|
||||||
Fleet Fleet `yaml:"fleet"`
|
Fleet Fleet `yaml:"fleet"`
|
||||||
OEM OEM `yaml:"oem"`
|
OEM OEM `yaml:"oem"`
|
||||||
Update Update `yaml:"update"`
|
Update Update `yaml:"update"`
|
||||||
@@ -29,16 +45,29 @@ type CloudConfig struct {
|
|||||||
NetworkConfig string `yaml:"-"`
|
NetworkConfig string `yaml:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsCloudConfig(userdata string) bool {
|
||||||
|
header := strings.SplitN(userdata, "\n", 2)[0]
|
||||||
|
|
||||||
|
// Explicitly trim the header so we can handle user-data from
|
||||||
|
// non-unix operating systems. The rest of the file is parsed
|
||||||
|
// by yaml, which correctly handles CRLF.
|
||||||
|
header = strings.TrimSuffix(header, "\r")
|
||||||
|
|
||||||
|
return (header == "#cloud-config")
|
||||||
|
}
|
||||||
|
|
||||||
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
// NewCloudConfig instantiates a new CloudConfig from the given contents (a
|
||||||
// string of YAML), returning any error encountered. It will ignore unknown
|
// string of YAML), returning any error encountered. It will ignore unknown
|
||||||
// fields but log encountering them.
|
// fields but log encountering them.
|
||||||
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
func NewCloudConfig(contents string) (*CloudConfig, error) {
|
||||||
var cfg CloudConfig
|
var cfg CloudConfig
|
||||||
err := yaml.Unmarshal([]byte(contents), &cfg)
|
ncontents, err := normalizeConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &cfg, err
|
return &cfg, err
|
||||||
}
|
}
|
||||||
warnOnUnrecognizedKeys(contents, log.Printf)
|
if err = yaml.Unmarshal(ncontents, &cfg); err != nil {
|
||||||
|
return &cfg, err
|
||||||
|
}
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,10 +89,20 @@ func IsZero(c interface{}) bool {
|
|||||||
return isZero(reflect.ValueOf(c))
|
return isZero(reflect.ValueOf(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
// AssertValid checks the fields in the structure and makes sure that they
|
type ErrorValid struct {
|
||||||
// contain valid values as specified by the 'valid' flag. Empty fields are
|
Value string
|
||||||
|
Valid []string
|
||||||
|
Field string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrorValid) Error() string {
|
||||||
|
return fmt.Sprintf("invalid value %q for option %q (valid options: %q)", e.Value, e.Field, e.Valid)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AssertStructValid checks the fields in the structure and makes sure that
|
||||||
|
// they contain valid values as specified by the 'valid' flag. Empty fields are
|
||||||
// implicitly valid.
|
// implicitly valid.
|
||||||
func AssertValid(c interface{}) error {
|
func AssertStructValid(c interface{}) error {
|
||||||
ct := reflect.TypeOf(c)
|
ct := reflect.TypeOf(c)
|
||||||
cv := reflect.ValueOf(c)
|
cv := reflect.ValueOf(c)
|
||||||
for i := 0; i < ct.NumField(); i++ {
|
for i := 0; i < ct.NumField(); i++ {
|
||||||
@@ -72,15 +111,33 @@ func AssertValid(c interface{}) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
valid := ft.Tag.Get("valid")
|
if err := AssertValid(cv.Field(i), ft.Tag.Get("valid")); err != nil {
|
||||||
val := cv.Field(i)
|
err.Field = ft.Name
|
||||||
if !isValid(val, valid) {
|
return err
|
||||||
return fmt.Errorf("invalid value \"%v\" for option %q (valid options: %q)", val.Interface(), ft.Name, valid)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AssertValid checks to make sure that the given value is in the list of
|
||||||
|
// valid values. Zero values are implicitly valid.
|
||||||
|
func AssertValid(value reflect.Value, valid string) *ErrorValid {
|
||||||
|
if valid == "" || isZero(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
vs := fmt.Sprintf("%v", value.Interface())
|
||||||
|
valids := strings.Split(valid, ",")
|
||||||
|
for _, valid := range valids {
|
||||||
|
if vs == valid {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &ErrorValid{
|
||||||
|
Value: vs,
|
||||||
|
Valid: valids,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func isZero(v reflect.Value) bool {
|
func isZero(v reflect.Value) bool {
|
||||||
switch v.Kind() {
|
switch v.Kind() {
|
||||||
case reflect.Struct:
|
case reflect.Struct:
|
||||||
@@ -100,99 +157,30 @@ func isFieldExported(f reflect.StructField) bool {
|
|||||||
return f.PkgPath == ""
|
return f.PkgPath == ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func isValid(v reflect.Value, valid string) bool {
|
func normalizeConfig(config string) ([]byte, error) {
|
||||||
if valid == "" || isZero(v) {
|
var cfg map[interface{}]interface{}
|
||||||
return true
|
if err := yaml.Unmarshal([]byte(config), &cfg); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
vs := fmt.Sprintf("%v", v.Interface())
|
return yaml.Marshal(normalizeKeys(cfg))
|
||||||
for _, valid := range strings.Split(valid, ",") {
|
|
||||||
if vs == valid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type warner func(format string, v ...interface{})
|
func normalizeKeys(m map[interface{}]interface{}) map[interface{}]interface{} {
|
||||||
|
for k, v := range m {
|
||||||
// warnOnUnrecognizedKeys parses the contents of a cloud-config file and calls
|
if m, ok := m[k].(map[interface{}]interface{}); ok {
|
||||||
// warn(msg, key) for every unrecognized key (i.e. those not present in CloudConfig)
|
normalizeKeys(m)
|
||||||
func warnOnUnrecognizedKeys(contents string, warn warner) {
|
|
||||||
// Generate a map of all understood cloud config options
|
|
||||||
var cc map[string]interface{}
|
|
||||||
b, _ := yaml.Marshal(&CloudConfig{})
|
|
||||||
yaml.Unmarshal(b, &cc)
|
|
||||||
|
|
||||||
// Now unmarshal the entire provided contents
|
|
||||||
var c map[string]interface{}
|
|
||||||
yaml.Unmarshal([]byte(contents), &c)
|
|
||||||
|
|
||||||
// Check that every key in the contents exists in the cloud config
|
|
||||||
for k, _ := range c {
|
|
||||||
if _, ok := cc[k]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in provided cloud config - ignoring section", k)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for unrecognized coreos options, if any are set
|
if s, ok := m[k].([]interface{}); ok {
|
||||||
if coreos, ok := c["coreos"]; ok {
|
for _, e := range s {
|
||||||
if set, ok := coreos.(map[interface{}]interface{}); ok {
|
if m, ok := e.(map[interface{}]interface{}); ok {
|
||||||
known := cc["coreos"].(map[interface{}]interface{})
|
normalizeKeys(m)
|
||||||
for k, _ := range set {
|
|
||||||
if key, ok := k.(string); ok {
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Warning: unrecognized key %q in coreos section of provided cloud config - ignoring", k)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for any badly-specified users, if any are set
|
delete(m, k)
|
||||||
if users, ok := c["users"]; ok {
|
m[strings.Replace(fmt.Sprint(k), "-", "_", -1)] = v
|
||||||
var known map[string]interface{}
|
|
||||||
b, _ := yaml.Marshal(&User{})
|
|
||||||
yaml.Unmarshal(b, &known)
|
|
||||||
|
|
||||||
if set, ok := users.([]interface{}); ok {
|
|
||||||
for _, u := range set {
|
|
||||||
if user, ok := u.(map[interface{}]interface{}); ok {
|
|
||||||
for k, _ := range user {
|
|
||||||
if key, ok := k.(string); ok {
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Warning: unrecognized key %q in user section of cloud config - ignoring", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for any badly-specified files, if any are set
|
|
||||||
if files, ok := c["write_files"]; ok {
|
|
||||||
var known map[string]interface{}
|
|
||||||
b, _ := yaml.Marshal(&File{})
|
|
||||||
yaml.Unmarshal(b, &known)
|
|
||||||
|
|
||||||
if set, ok := files.([]interface{}); ok {
|
|
||||||
for _, f := range set {
|
|
||||||
if file, ok := f.(map[interface{}]interface{}); ok {
|
|
||||||
for k, _ := range file {
|
|
||||||
if key, ok := k.(string); ok {
|
|
||||||
if _, ok := known[key]; !ok {
|
|
||||||
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", key)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
warn("Warning: unrecognized key %q in file section of cloud config - ignoring", k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
@@ -1,8 +1,22 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -22,12 +36,12 @@ func TestIsZero(t *testing.T) {
|
|||||||
{struct{ A int }{A: 1}, false},
|
{struct{ A int }{A: 1}, false},
|
||||||
} {
|
} {
|
||||||
if empty := IsZero(tt.c); tt.empty != empty {
|
if empty := IsZero(tt.c); tt.empty != empty {
|
||||||
t.Errorf("bad result (%q): want %q, got %q", tt.c, tt.empty, empty)
|
t.Errorf("bad result (%q): want %t, got %t", tt.c, tt.empty, empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAssertValid(t *testing.T) {
|
func TestAssertStructValid(t *testing.T) {
|
||||||
for _, tt := range []struct {
|
for _, tt := range []struct {
|
||||||
c interface{}
|
c interface{}
|
||||||
err error
|
err error
|
||||||
@@ -44,7 +58,7 @@ func TestAssertValid(t *testing.T) {
|
|||||||
}{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"}, errors.New("invalid value \"hello\" for option \"A\" (valid options: \"1,2\")")},
|
}{A: "hello", b: "2"}, &ErrorValid{Value: "hello", Field: "A", Valid: []string{"1", "2"}}},
|
||||||
{struct {
|
{struct {
|
||||||
A, b int `valid:"1,2"`
|
A, b int `valid:"1,2"`
|
||||||
}{}, nil},
|
}{}, nil},
|
||||||
@@ -56,9 +70,9 @@ func TestAssertValid(t *testing.T) {
|
|||||||
}{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}, errors.New("invalid value \"9\" for option \"A\" (valid options: \"1,2\")")},
|
}{A: 9, b: 2}, &ErrorValid{Value: "9", Field: "A", Valid: []string{"1", "2"}}},
|
||||||
} {
|
} {
|
||||||
if err := AssertValid(tt.c); !reflect.DeepEqual(tt.err, err) {
|
if err := AssertStructValid(tt.c); !reflect.DeepEqual(tt.err, err) {
|
||||||
t.Errorf("bad result (%q): want %q, got %q", tt.c, tt.err, err)
|
t.Errorf("bad result (%q): want %q, got %q", tt.c, tt.err, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,29 +145,6 @@ hostname:
|
|||||||
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
|
if len(cfg.Users) < 1 || cfg.Users[0].Name != "fry" || cfg.Users[0].PasswordHash != "somehash" {
|
||||||
t.Fatalf("users section not correctly set when invalid keys are present")
|
t.Fatalf("users section not correctly set when invalid keys are present")
|
||||||
}
|
}
|
||||||
|
|
||||||
var warnings string
|
|
||||||
catchWarn := func(f string, v ...interface{}) {
|
|
||||||
warnings += fmt.Sprintf(f, v...)
|
|
||||||
}
|
|
||||||
|
|
||||||
warnOnUnrecognizedKeys(contents, catchWarn)
|
|
||||||
|
|
||||||
if !strings.Contains(warnings, "coreos_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized coreos option coreos_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "bare_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized key bare_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "section_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized key section_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "user_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized user key user_unknown")
|
|
||||||
}
|
|
||||||
if !strings.Contains(warnings, "file_unknown") {
|
|
||||||
t.Errorf("warnings did not catch unrecognized file key file_unknown")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the parsing of a cloud config file "generally works"
|
// Assert that the parsing of a cloud config file "generally works"
|
||||||
@@ -184,7 +175,7 @@ coreos:
|
|||||||
etcd:
|
etcd:
|
||||||
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
discovery: "https://discovery.etcd.io/827c73219eeb2fa5530027c37bf18877"
|
||||||
update:
|
update:
|
||||||
reboot-strategy: reboot
|
reboot_strategy: reboot
|
||||||
units:
|
units:
|
||||||
- name: 50-eth0.network
|
- name: 50-eth0.network
|
||||||
runtime: yes
|
runtime: yes
|
||||||
@@ -201,9 +192,9 @@ coreos:
|
|||||||
oem:
|
oem:
|
||||||
id: rackspace
|
id: rackspace
|
||||||
name: Rackspace Cloud Servers
|
name: Rackspace Cloud Servers
|
||||||
version-id: 168.0.0
|
version_id: 168.0.0
|
||||||
home-url: https://www.rackspace.com/cloud/servers/
|
home_url: https://www.rackspace.com/cloud/servers/
|
||||||
bug-report-url: https://github.com/coreos/coreos-overlay
|
bug_report_url: https://github.com/coreos/coreos-overlay
|
||||||
ssh_authorized_keys:
|
ssh_authorized_keys:
|
||||||
- foobar
|
- foobar
|
||||||
- foobaz
|
- foobaz
|
||||||
@@ -338,18 +329,18 @@ func TestCloudConfigUsers(t *testing.T) {
|
|||||||
users:
|
users:
|
||||||
- name: elroy
|
- name: elroy
|
||||||
passwd: somehash
|
passwd: somehash
|
||||||
ssh-authorized-keys:
|
ssh_authorized_keys:
|
||||||
- somekey
|
- somekey
|
||||||
gecos: arbitrary comment
|
gecos: arbitrary comment
|
||||||
homedir: /home/place
|
homedir: /home/place
|
||||||
no-create-home: yes
|
no_create_home: yes
|
||||||
primary-group: things
|
primary_group: things
|
||||||
groups:
|
groups:
|
||||||
- ping
|
- ping
|
||||||
- pong
|
- pong
|
||||||
no-user-group: true
|
no_user_group: true
|
||||||
system: y
|
system: y
|
||||||
no-log-init: True
|
no_log_init: True
|
||||||
`
|
`
|
||||||
cfg, err := NewCloudConfig(contents)
|
cfg, err := NewCloudConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -357,7 +348,7 @@ users:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Users) != 1 {
|
if len(cfg.Users) != 1 {
|
||||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
|
||||||
}
|
}
|
||||||
|
|
||||||
user := cfg.Users[0]
|
user := cfg.Users[0]
|
||||||
@@ -388,11 +379,11 @@ users:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !user.NoCreateHome {
|
if !user.NoCreateHome {
|
||||||
t.Errorf("Failed to parse no-create-home field")
|
t.Errorf("Failed to parse no_create_home field")
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.PrimaryGroup != "things" {
|
if user.PrimaryGroup != "things" {
|
||||||
t.Errorf("Failed to parse primary-group field, got %q", user.PrimaryGroup)
|
t.Errorf("Failed to parse primary_group field, got %q", user.PrimaryGroup)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(user.Groups) != 2 {
|
if len(user.Groups) != 2 {
|
||||||
@@ -407,7 +398,7 @@ users:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !user.NoUserGroup {
|
if !user.NoUserGroup {
|
||||||
t.Errorf("Failed to parse no-user-group field")
|
t.Errorf("Failed to parse no_user_group field")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !user.System {
|
if !user.System {
|
||||||
@@ -415,7 +406,7 @@ users:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !user.NoLogInit {
|
if !user.NoLogInit {
|
||||||
t.Errorf("Failed to parse no-log-init field")
|
t.Errorf("Failed to parse no_log_init field")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,7 +415,7 @@ func TestCloudConfigUsersGithubUser(t *testing.T) {
|
|||||||
contents := `
|
contents := `
|
||||||
users:
|
users:
|
||||||
- name: elroy
|
- name: elroy
|
||||||
coreos-ssh-import-github: bcwaldon
|
coreos_ssh_import_github: bcwaldon
|
||||||
`
|
`
|
||||||
cfg, err := NewCloudConfig(contents)
|
cfg, err := NewCloudConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -432,7 +423,7 @@ users:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Users) != 1 {
|
if len(cfg.Users) != 1 {
|
||||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
|
||||||
}
|
}
|
||||||
|
|
||||||
user := cfg.Users[0]
|
user := cfg.Users[0]
|
||||||
@@ -450,7 +441,7 @@ func TestCloudConfigUsersSSHImportURL(t *testing.T) {
|
|||||||
contents := `
|
contents := `
|
||||||
users:
|
users:
|
||||||
- name: elroy
|
- name: elroy
|
||||||
coreos-ssh-import-url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
|
coreos_ssh_import_url: https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys
|
||||||
`
|
`
|
||||||
cfg, err := NewCloudConfig(contents)
|
cfg, err := NewCloudConfig(contents)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -458,7 +449,7 @@ users:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cfg.Users) != 1 {
|
if len(cfg.Users) != 1 {
|
||||||
t.Fatalf("Parsed %d users, expected 1", cfg.Users)
|
t.Fatalf("Parsed %d users, expected 1", len(cfg.Users))
|
||||||
}
|
}
|
||||||
|
|
||||||
user := cfg.Users[0]
|
user := cfg.Users[0]
|
||||||
@@ -471,3 +462,30 @@ users:
|
|||||||
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
|
t.Errorf("ssh import url is %q, expected 'https://token:x-auth-token@github.enterprise.com/api/v3/polvi/keys'", user.SSHImportURL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNormalizeKeys(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
in string
|
||||||
|
out string
|
||||||
|
}{
|
||||||
|
{"my_key_name: the-value\n", "my_key_name: the-value\n"},
|
||||||
|
{"my-key_name: the-value\n", "my_key_name: the-value\n"},
|
||||||
|
{"my-key-name: the-value\n", "my_key_name: the-value\n"},
|
||||||
|
|
||||||
|
{"a:\n- key_name: the-value\n", "a:\n- key_name: the-value\n"},
|
||||||
|
{"a:\n- key-name: the-value\n", "a:\n- key_name: the-value\n"},
|
||||||
|
|
||||||
|
{"a:\n b:\n - key_name: the-value\n", "a:\n b:\n - key_name: the-value\n"},
|
||||||
|
{"a:\n b:\n - key-name: the-value\n", "a:\n b:\n - key_name: the-value\n"},
|
||||||
|
|
||||||
|
{"coreos:\n update:\n reboot-strategy: off\n", "coreos:\n update:\n reboot_strategy: false\n"},
|
||||||
|
} {
|
||||||
|
out, err := normalizeConfig(tt.in)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("bad error (%q): want nil, got %s", tt.in, err)
|
||||||
|
}
|
||||||
|
if string(out) != tt.out {
|
||||||
|
t.Fatalf("bad normalization (%q): want %q, got %q", tt.in, tt.out, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
type EtcHosts string
|
type EtcHosts string
|
||||||
|
@@ -1,32 +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
|
package config
|
||||||
|
|
||||||
type Etcd struct {
|
type Etcd struct {
|
||||||
Addr string `yaml:"addr" env:"ETCD_ADDR"`
|
Addr string `yaml:"addr" env:"ETCD_ADDR"`
|
||||||
BindAddr string `yaml:"bind-addr" env:"ETCD_BIND_ADDR"`
|
BindAddr string `yaml:"bind_addr" env:"ETCD_BIND_ADDR"`
|
||||||
CAFile string `yaml:"ca-file" env:"ETCD_CA_FILE"`
|
CAFile string `yaml:"ca_file" env:"ETCD_CA_FILE"`
|
||||||
CertFile string `yaml:"cert-file" env:"ETCD_CERT_FILE"`
|
CertFile string `yaml:"cert_file" env:"ETCD_CERT_FILE"`
|
||||||
ClusterActiveSize string `yaml:"cluster-active-size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
ClusterActiveSize string `yaml:"cluster_active_size" env:"ETCD_CLUSTER_ACTIVE_SIZE"`
|
||||||
ClusterRemoveDelay string `yaml:"cluster-remove-delay" env:"ETCD_CLUSTER_REMOVE_DELAY"`
|
ClusterRemoveDelay string `yaml:"cluster_remove_delay" env:"ETCD_CLUSTER_REMOVE_DELAY"`
|
||||||
ClusterSyncInterval string `yaml:"cluster-sync-interval" env:"ETCD_CLUSTER_SYNC_INTERVAL"`
|
ClusterSyncInterval string `yaml:"cluster_sync_interval" env:"ETCD_CLUSTER_SYNC_INTERVAL"`
|
||||||
Cors string `yaml:"cors" env:"ETCD_CORS"`
|
Cors string `yaml:"cors" env:"ETCD_CORS"`
|
||||||
CPUProfileFile string `yaml:"cpu-profile-file" env:"ETCD_CPU_PROFILE_FILE"`
|
CPUProfileFile string `yaml:"cpu_profile_file" env:"ETCD_CPU_PROFILE_FILE"`
|
||||||
DataDir string `yaml:"data-dir" env:"ETCD_DATA_DIR"`
|
DataDir string `yaml:"data_dir" env:"ETCD_DATA_DIR"`
|
||||||
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
Discovery string `yaml:"discovery" env:"ETCD_DISCOVERY"`
|
||||||
HTTPReadTimeout string `yaml:"http-read-timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
HTTPReadTimeout string `yaml:"http_read_timeout" env:"ETCD_HTTP_READ_TIMEOUT"`
|
||||||
HTTPWriteTimeout string `yaml:"http-write-timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
HTTPWriteTimeout string `yaml:"http_write_timeout" env:"ETCD_HTTP_WRITE_TIMEOUT"`
|
||||||
KeyFile string `yaml:"key-file" env:"ETCD_KEY_FILE"`
|
KeyFile string `yaml:"key_file" env:"ETCD_KEY_FILE"`
|
||||||
MaxClusterSize string `yaml:"max-cluster-size" env:"ETCD_MAX_CLUSTER_SIZE"`
|
MaxClusterSize string `yaml:"max_cluster_size" env:"ETCD_MAX_CLUSTER_SIZE"`
|
||||||
MaxResultBuffer string `yaml:"max-result-buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
MaxResultBuffer string `yaml:"max_result_buffer" env:"ETCD_MAX_RESULT_BUFFER"`
|
||||||
MaxRetryAttempts string `yaml:"max-retry-attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
MaxRetryAttempts string `yaml:"max_retry_attempts" env:"ETCD_MAX_RETRY_ATTEMPTS"`
|
||||||
Name string `yaml:"name" env:"ETCD_NAME"`
|
Name string `yaml:"name" env:"ETCD_NAME"`
|
||||||
PeerAddr string `yaml:"peer-addr" env:"ETCD_PEER_ADDR"`
|
PeerAddr string `yaml:"peer_addr" env:"ETCD_PEER_ADDR"`
|
||||||
PeerBindAddr string `yaml:"peer-bind-addr" env:"ETCD_PEER_BIND_ADDR"`
|
PeerBindAddr string `yaml:"peer_bind_addr" env:"ETCD_PEER_BIND_ADDR"`
|
||||||
PeerCAFile string `yaml:"peer-ca-file" env:"ETCD_PEER_CA_FILE"`
|
PeerCAFile string `yaml:"peer_ca_file" env:"ETCD_PEER_CA_FILE"`
|
||||||
PeerCertFile string `yaml:"peer-cert-file" env:"ETCD_PEER_CERT_FILE"`
|
PeerCertFile string `yaml:"peer_cert_file" env:"ETCD_PEER_CERT_FILE"`
|
||||||
PeerKeyFile string `yaml:"peer-key-file" env:"ETCD_PEER_KEY_FILE"`
|
PeerKeyFile string `yaml:"peer_key_file" env:"ETCD_PEER_KEY_FILE"`
|
||||||
Peers string `yaml:"peers" env:"ETCD_PEERS"`
|
Peers string `yaml:"peers" env:"ETCD_PEERS"`
|
||||||
PeersFile string `yaml:"peers-file" env:"ETCD_PEERS_FILE"`
|
PeersFile string `yaml:"peers_file" env:"ETCD_PEERS_FILE"`
|
||||||
Snapshot string `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
|
Snapshot string `yaml:"snapshot" env:"ETCD_SNAPSHOT"`
|
||||||
Verbose string `yaml:"verbose" env:"ETCD_VERBOSE"`
|
Verbose string `yaml:"verbose" env:"ETCD_VERBOSE"`
|
||||||
VeryVerbose string `yaml:"very-verbose" env:"ETCD_VERY_VERBOSE"`
|
VeryVerbose string `yaml:"very_verbose" env:"ETCD_VERY_VERBOSE"`
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
|
9
config/flannel.go
Normal file
9
config/flannel.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Flannel struct {
|
||||||
|
EtcdEndpoint string `yaml:"etcd-endpoint" env:"FLANNELD_ETCD_ENDPOINT"`
|
||||||
|
EtcdPrefix string `yaml:"etcd-prefix" env:"FLANNELD_ETCD_PREFIX"`
|
||||||
|
IPMasq string `yaml:"ip-masq" env:"FLANNELD_IP_MASQ"`
|
||||||
|
SubnetFile string `yaml:"subnet-file" env:"FLANNELD_SUBNET_FILE"`
|
||||||
|
Iface string `yaml:"interface" env:"FLANNELD_IFACE"`
|
||||||
|
}
|
@@ -1,14 +1,30 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
type Fleet struct {
|
type Fleet struct {
|
||||||
AgentTTL string `yaml:"agent-ttl" env:"FLEET_AGENT_TTL"`
|
AgentTTL string `yaml:"agent_ttl" env:"FLEET_AGENT_TTL"`
|
||||||
EngineReconcileInterval string `yaml:"engine-reconcile-interval" env:"FLEET_ENGINE_RECONCILE_INTERVAL"`
|
EngineReconcileInterval string `yaml:"engine_reconcile_interval" env:"FLEET_ENGINE_RECONCILE_INTERVAL"`
|
||||||
EtcdCAFile string `yaml:"etcd-cafile" env:"FLEET_ETCD_CAFILE"`
|
EtcdCAFile string `yaml:"etcd_cafile" env:"FLEET_ETCD_CAFILE"`
|
||||||
EtcdCertFile string `yaml:"etcd-certfile" env:"FLEET_ETCD_CERTFILE"`
|
EtcdCertFile string `yaml:"etcd_certfile" env:"FLEET_ETCD_CERTFILE"`
|
||||||
EtcdKeyFile string `yaml:"etcd-keyfile" env:"FLEET_ETCD_KEYFILE"`
|
EtcdKeyFile string `yaml:"etcd_keyfile" env:"FLEET_ETCD_KEYFILE"`
|
||||||
EtcdRequestTimeout string `yaml:"etcd-request-timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
|
EtcdRequestTimeout string `yaml:"etcd_request_timeout" env:"FLEET_ETCD_REQUEST_TIMEOUT"`
|
||||||
EtcdServers string `yaml:"etcd-servers" env:"FLEET_ETCD_SERVERS"`
|
EtcdServers string `yaml:"etcd_servers" env:"FLEET_ETCD_SERVERS"`
|
||||||
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
|
Metadata string `yaml:"metadata" env:"FLEET_METADATA"`
|
||||||
PublicIP string `yaml:"public-ip" env:"FLEET_PUBLIC_IP"`
|
PublicIP string `yaml:"public_ip" env:"FLEET_PUBLIC_IP"`
|
||||||
Verbosity string `yaml:"verbosity" env:"FLEET_VERBOSITY"`
|
Verbosity string `yaml:"verbosity" env:"FLEET_VERBOSITY"`
|
||||||
}
|
}
|
||||||
|
@@ -1,9 +1,25 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
type OEM struct {
|
type OEM struct {
|
||||||
ID string `yaml:"id"`
|
ID string `yaml:"id"`
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
VersionID string `yaml:"version-id"`
|
VersionID string `yaml:"version_id"`
|
||||||
HomeURL string `yaml:"home-url"`
|
HomeURL string `yaml:"home_url"`
|
||||||
BugReportURL string `yaml:"bug-report-url"`
|
BugReportURL string `yaml:"bug_report_url"`
|
||||||
}
|
}
|
||||||
|
32
config/script.go
Normal file
32
config/script.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
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 (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Script []byte
|
||||||
|
|
||||||
|
func IsScript(userdata string) bool {
|
||||||
|
header := strings.SplitN(userdata, "\n", 2)[0]
|
||||||
|
return strings.HasPrefix(header, "#!")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewScript(userdata string) (Script, error) {
|
||||||
|
return Script(userdata), nil
|
||||||
|
}
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -11,7 +27,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"`
|
Command string `yaml:"command" valid:"start,stop,restart,reload,try-restart,reload-or-restart,reload-or-try-restart"`
|
||||||
|
|
||||||
// For drop-in units, a cloudinit.conf is generated.
|
// For drop-in units, a cloudinit.conf is generated.
|
||||||
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
// This is currently unbound in YAML (and hence unsettable in cloud-config files)
|
||||||
|
@@ -1,7 +1,23 @@
|
|||||||
|
/*
|
||||||
|
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
|
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,false"`
|
||||||
Group string `yaml:"group" env:"GROUP"`
|
Group string `yaml:"group" env:"GROUP"`
|
||||||
Server string `yaml:"server" env:"SERVER"`
|
Server string `yaml:"server" env:"SERVER"`
|
||||||
}
|
}
|
||||||
|
@@ -1,17 +1,33 @@
|
|||||||
|
/*
|
||||||
|
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
|
package config
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
Name string `yaml:"name"`
|
Name string `yaml:"name"`
|
||||||
PasswordHash string `yaml:"passwd"`
|
PasswordHash string `yaml:"passwd"`
|
||||||
SSHAuthorizedKeys []string `yaml:"ssh-authorized-keys"`
|
SSHAuthorizedKeys []string `yaml:"ssh_authorized_keys"`
|
||||||
SSHImportGithubUser string `yaml:"coreos-ssh-import-github"`
|
SSHImportGithubUser string `yaml:"coreos_ssh_import_github"`
|
||||||
SSHImportURL string `yaml:"coreos-ssh-import-url"`
|
SSHImportURL string `yaml:"coreos_ssh_import_url"`
|
||||||
GECOS string `yaml:"gecos"`
|
GECOS string `yaml:"gecos"`
|
||||||
Homedir string `yaml:"homedir"`
|
Homedir string `yaml:"homedir"`
|
||||||
NoCreateHome bool `yaml:"no-create-home"`
|
NoCreateHome bool `yaml:"no_create_home"`
|
||||||
PrimaryGroup string `yaml:"primary-group"`
|
PrimaryGroup string `yaml:"primary_group"`
|
||||||
Groups []string `yaml:"groups"`
|
Groups []string `yaml:"groups"`
|
||||||
NoUserGroup bool `yaml:"no-user-group"`
|
NoUserGroup bool `yaml:"no_user_group"`
|
||||||
System bool `yaml:"system"`
|
System bool `yaml:"system"`
|
||||||
NoLogInit bool `yaml:"no-log-init"`
|
NoLogInit bool `yaml:"no_log_init"`
|
||||||
}
|
}
|
||||||
|
54
config/validate/context.go
Normal file
54
config/validate/context.go
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
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 validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// context represents the current position within a newline-delimited string.
|
||||||
|
// Each line is loaded, one by one, into currentLine (newline omitted) and
|
||||||
|
// lineNumber keeps track of its position within the original string.
|
||||||
|
type context struct {
|
||||||
|
currentLine string
|
||||||
|
remainingLines string
|
||||||
|
lineNumber int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Increment moves the context to the next line (if available).
|
||||||
|
func (c *context) Increment() {
|
||||||
|
if c.currentLine == "" && c.remainingLines == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
lines := strings.SplitN(c.remainingLines, "\n", 2)
|
||||||
|
c.currentLine = lines[0]
|
||||||
|
if len(lines) == 2 {
|
||||||
|
c.remainingLines = lines[1]
|
||||||
|
} else {
|
||||||
|
c.remainingLines = ""
|
||||||
|
}
|
||||||
|
c.lineNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContext creates a context from the provided data. It strips out all
|
||||||
|
// carriage returns and moves to the first line (if available).
|
||||||
|
func NewContext(content []byte) context {
|
||||||
|
c := context{remainingLines: strings.Replace(string(content), "\r", "", -1)}
|
||||||
|
c.Increment()
|
||||||
|
return c
|
||||||
|
}
|
133
config/validate/context_test.go
Normal file
133
config/validate/context_test.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
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 validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewContext(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
in string
|
||||||
|
|
||||||
|
out context
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
out: context{
|
||||||
|
currentLine: "",
|
||||||
|
remainingLines: "",
|
||||||
|
lineNumber: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
in: "this\r\nis\r\na\r\ntest",
|
||||||
|
out: context{
|
||||||
|
currentLine: "this",
|
||||||
|
remainingLines: "is\na\ntest",
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if out := NewContext([]byte(tt.in)); !reflect.DeepEqual(tt.out, out) {
|
||||||
|
t.Errorf("bad context (%q): want %#v, got %#v", tt.in, tt.out, out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIncrement(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
init context
|
||||||
|
op func(c *context)
|
||||||
|
|
||||||
|
res context
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
init: context{
|
||||||
|
currentLine: "",
|
||||||
|
remainingLines: "",
|
||||||
|
lineNumber: 0,
|
||||||
|
},
|
||||||
|
res: context{
|
||||||
|
currentLine: "",
|
||||||
|
remainingLines: "",
|
||||||
|
lineNumber: 0,
|
||||||
|
},
|
||||||
|
op: func(c *context) {
|
||||||
|
c.Increment()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
init: context{
|
||||||
|
currentLine: "test",
|
||||||
|
remainingLines: "",
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
res: context{
|
||||||
|
currentLine: "",
|
||||||
|
remainingLines: "",
|
||||||
|
lineNumber: 2,
|
||||||
|
},
|
||||||
|
op: func(c *context) {
|
||||||
|
c.Increment()
|
||||||
|
c.Increment()
|
||||||
|
c.Increment()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
init: context{
|
||||||
|
currentLine: "this",
|
||||||
|
remainingLines: "is\na\ntest",
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
res: context{
|
||||||
|
currentLine: "is",
|
||||||
|
remainingLines: "a\ntest",
|
||||||
|
lineNumber: 2,
|
||||||
|
},
|
||||||
|
op: func(c *context) {
|
||||||
|
c.Increment()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
init: context{
|
||||||
|
currentLine: "this",
|
||||||
|
remainingLines: "is\na\ntest",
|
||||||
|
lineNumber: 1,
|
||||||
|
},
|
||||||
|
res: context{
|
||||||
|
currentLine: "test",
|
||||||
|
remainingLines: "",
|
||||||
|
lineNumber: 4,
|
||||||
|
},
|
||||||
|
op: func(c *context) {
|
||||||
|
c.Increment()
|
||||||
|
c.Increment()
|
||||||
|
c.Increment()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tt := range tests {
|
||||||
|
res := tt.init
|
||||||
|
if tt.op(&res); !reflect.DeepEqual(tt.res, res) {
|
||||||
|
t.Errorf("bad context (%d, %#v): want %#v, got %#v", i, tt.init, tt.res, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
159
config/validate/node.go
Normal file
159
config/validate/node.go
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
/*
|
||||||
|
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 validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
yamlKey = regexp.MustCompile(`^ *-? ?(?P<key>.*?):`)
|
||||||
|
yamlElem = regexp.MustCompile(`^ *-`)
|
||||||
|
)
|
||||||
|
|
||||||
|
type node struct {
|
||||||
|
name string
|
||||||
|
line int
|
||||||
|
children []node
|
||||||
|
field reflect.StructField
|
||||||
|
reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Child attempts to find the child with the given name in the node's list of
|
||||||
|
// children. If no such child is found, an invalid node is returned.
|
||||||
|
func (n node) Child(name string) node {
|
||||||
|
for _, c := range n.children {
|
||||||
|
if c.name == name {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HumanType returns the human-consumable string representation of the type of
|
||||||
|
// the node.
|
||||||
|
func (n node) HumanType() string {
|
||||||
|
switch k := n.Kind(); k {
|
||||||
|
case reflect.Slice:
|
||||||
|
c := n.Type().Elem()
|
||||||
|
return "[]" + node{Value: reflect.New(c).Elem()}.HumanType()
|
||||||
|
default:
|
||||||
|
return k.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNode returns the node representation of the given value. The context
|
||||||
|
// will be used in an attempt to determine line numbers for the given value.
|
||||||
|
func NewNode(value interface{}, context context) node {
|
||||||
|
var n node
|
||||||
|
toNode(value, context, &n)
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
// toNode converts the given value into a node and then recursively processes
|
||||||
|
// each of the nodes components (e.g. fields, array elements, keys).
|
||||||
|
func toNode(v interface{}, c context, n *node) {
|
||||||
|
vv := reflect.ValueOf(v)
|
||||||
|
if !vv.IsValid() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
n.Value = vv
|
||||||
|
switch vv.Kind() {
|
||||||
|
case reflect.Struct:
|
||||||
|
// Walk over each field in the structure, skipping unexported fields,
|
||||||
|
// and create a node for it.
|
||||||
|
for i := 0; i < vv.Type().NumField(); i++ {
|
||||||
|
ft := vv.Type().Field(i)
|
||||||
|
k := ft.Tag.Get("yaml")
|
||||||
|
if k == "-" || k == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cn := node{name: k, field: ft}
|
||||||
|
c, ok := findKey(cn.name, c)
|
||||||
|
if ok {
|
||||||
|
cn.line = c.lineNumber
|
||||||
|
}
|
||||||
|
toNode(vv.Field(i).Interface(), c, &cn)
|
||||||
|
n.children = append(n.children, cn)
|
||||||
|
}
|
||||||
|
case reflect.Map:
|
||||||
|
// Walk over each key in the map and create a node for it.
|
||||||
|
v := v.(map[interface{}]interface{})
|
||||||
|
for k, cv := range v {
|
||||||
|
cn := node{name: fmt.Sprintf("%s", k)}
|
||||||
|
c, ok := findKey(cn.name, c)
|
||||||
|
if ok {
|
||||||
|
cn.line = c.lineNumber
|
||||||
|
}
|
||||||
|
toNode(cv, c, &cn)
|
||||||
|
n.children = append(n.children, cn)
|
||||||
|
}
|
||||||
|
case reflect.Slice:
|
||||||
|
// Walk over each element in the slice and create a node for it.
|
||||||
|
// While iterating over the slice, preserve the context after it
|
||||||
|
// is modified. This allows the line numbers to reflect the current
|
||||||
|
// element instead of the first.
|
||||||
|
for i := 0; i < vv.Len(); i++ {
|
||||||
|
cn := node{
|
||||||
|
name: fmt.Sprintf("%s[%d]", n.name, i),
|
||||||
|
field: n.field,
|
||||||
|
}
|
||||||
|
var ok bool
|
||||||
|
c, ok = findElem(c)
|
||||||
|
if ok {
|
||||||
|
cn.line = c.lineNumber
|
||||||
|
}
|
||||||
|
toNode(vv.Index(i).Interface(), c, &cn)
|
||||||
|
n.children = append(n.children, cn)
|
||||||
|
c.Increment()
|
||||||
|
}
|
||||||
|
case reflect.String, reflect.Int, reflect.Bool:
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("toNode(): unhandled kind %s", vv.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// findKey attempts to find the requested key within the provided context.
|
||||||
|
// A modified copy of the context is returned with every line up to the key
|
||||||
|
// incremented past. A boolean, true if the key was found, is also returned.
|
||||||
|
func findKey(key string, context context) (context, bool) {
|
||||||
|
return find(yamlKey, key, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// findElem attempts to find an array element within the provided context.
|
||||||
|
// A modified copy of the context is returned with every line up to the array
|
||||||
|
// element incremented past. A boolean, true if the key was found, is also
|
||||||
|
// returned.
|
||||||
|
func findElem(context context) (context, bool) {
|
||||||
|
return find(yamlElem, "", context)
|
||||||
|
}
|
||||||
|
|
||||||
|
func find(exp *regexp.Regexp, key string, context context) (context, bool) {
|
||||||
|
for len(context.currentLine) > 0 || len(context.remainingLines) > 0 {
|
||||||
|
matches := exp.FindStringSubmatch(context.currentLine)
|
||||||
|
if len(matches) > 0 && (key == "" || matches[1] == key) {
|
||||||
|
return context, true
|
||||||
|
}
|
||||||
|
|
||||||
|
context.Increment()
|
||||||
|
}
|
||||||
|
return context, false
|
||||||
|
}
|
286
config/validate/node_test.go
Normal file
286
config/validate/node_test.go
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
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 validate
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestChild(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
parent node
|
||||||
|
name string
|
||||||
|
|
||||||
|
child node
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
name: "c1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parent: node{
|
||||||
|
children: []node{
|
||||||
|
node{name: "c1"},
|
||||||
|
node{name: "c2"},
|
||||||
|
node{name: "c3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parent: node{
|
||||||
|
children: []node{
|
||||||
|
node{name: "c1"},
|
||||||
|
node{name: "c2"},
|
||||||
|
node{name: "c3"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
name: "c2",
|
||||||
|
child: node{name: "c2"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if child := tt.parent.Child(tt.name); !reflect.DeepEqual(tt.child, child) {
|
||||||
|
t.Errorf("bad child (%q): want %#v, got %#v", tt.name, tt.child, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHumanType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
node node
|
||||||
|
|
||||||
|
humanType string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
humanType: "invalid",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: node{Value: reflect.ValueOf("hello")},
|
||||||
|
humanType: "string",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
node: node{
|
||||||
|
Value: reflect.ValueOf([]int{1, 2}),
|
||||||
|
children: []node{
|
||||||
|
node{Value: reflect.ValueOf(1)},
|
||||||
|
node{Value: reflect.ValueOf(2)},
|
||||||
|
}},
|
||||||
|
humanType: "[]int",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if humanType := tt.node.HumanType(); tt.humanType != humanType {
|
||||||
|
t.Errorf("bad type (%q): want %q, got %q", tt.node, tt.humanType, humanType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToNode(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
value interface{}
|
||||||
|
context context
|
||||||
|
|
||||||
|
node node
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
value: struct{}{},
|
||||||
|
node: node{Value: reflect.ValueOf(struct{}{})},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: struct {
|
||||||
|
A int `yaml:"a"`
|
||||||
|
}{},
|
||||||
|
node: node{
|
||||||
|
children: []node{
|
||||||
|
node{
|
||||||
|
name: "a",
|
||||||
|
field: reflect.TypeOf(struct {
|
||||||
|
A int `yaml:"a"`
|
||||||
|
}{}).Field(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: struct {
|
||||||
|
A []int `yaml:"a"`
|
||||||
|
}{},
|
||||||
|
node: node{
|
||||||
|
children: []node{
|
||||||
|
node{
|
||||||
|
name: "a",
|
||||||
|
field: reflect.TypeOf(struct {
|
||||||
|
A []int `yaml:"a"`
|
||||||
|
}{}).Field(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: map[interface{}]interface{}{
|
||||||
|
"a": map[interface{}]interface{}{
|
||||||
|
"b": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
context: NewContext([]byte("a:\n b: 2")),
|
||||||
|
node: node{
|
||||||
|
children: []node{
|
||||||
|
node{
|
||||||
|
line: 1,
|
||||||
|
name: "a",
|
||||||
|
children: []node{
|
||||||
|
node{name: "b", line: 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: struct {
|
||||||
|
A struct {
|
||||||
|
Jon bool `yaml:"b"`
|
||||||
|
} `yaml:"a"`
|
||||||
|
}{},
|
||||||
|
node: node{
|
||||||
|
children: []node{
|
||||||
|
node{
|
||||||
|
name: "a",
|
||||||
|
children: []node{
|
||||||
|
node{
|
||||||
|
name: "b",
|
||||||
|
field: reflect.TypeOf(struct {
|
||||||
|
Jon bool `yaml:"b"`
|
||||||
|
}{}).Field(0),
|
||||||
|
Value: reflect.ValueOf(false),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: reflect.TypeOf(struct {
|
||||||
|
A struct {
|
||||||
|
Jon bool `yaml:"b"`
|
||||||
|
} `yaml:"a"`
|
||||||
|
}{}).Field(0),
|
||||||
|
Value: reflect.ValueOf(struct {
|
||||||
|
Jon bool `yaml:"b"`
|
||||||
|
}{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Value: reflect.ValueOf(struct {
|
||||||
|
A struct {
|
||||||
|
Jon bool `yaml:"b"`
|
||||||
|
} `yaml:"a"`
|
||||||
|
}{}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
var node node
|
||||||
|
toNode(tt.value, tt.context, &node)
|
||||||
|
if !nodesEqual(tt.node, node) {
|
||||||
|
t.Errorf("bad node (%#v): want %#v, got %#v", tt.value, tt.node, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindKey(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
key string
|
||||||
|
context context
|
||||||
|
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
key: "key1",
|
||||||
|
context: NewContext([]byte("key1: hi")),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "key2",
|
||||||
|
context: NewContext([]byte("key1: hi")),
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "key3",
|
||||||
|
context: NewContext([]byte("key1:\n key2:\n key3: hi")),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "key4",
|
||||||
|
context: NewContext([]byte("key1:\n - key4: hi")),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "key5",
|
||||||
|
context: NewContext([]byte("#key5")),
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if _, found := findKey(tt.key, tt.context); tt.found != found {
|
||||||
|
t.Errorf("bad find (%q): want %t, got %t", tt.key, tt.found, found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFindElem(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
context context
|
||||||
|
|
||||||
|
found bool
|
||||||
|
}{
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
context: NewContext([]byte("test: hi")),
|
||||||
|
found: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: NewContext([]byte("test:\n - a\n -b")),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
context: NewContext([]byte("test:\n -\n a")),
|
||||||
|
found: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
if _, found := findElem(tt.context); tt.found != found {
|
||||||
|
t.Errorf("bad find (%q): want %t, got %t", tt.context, tt.found, found)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func nodesEqual(a, b node) bool {
|
||||||
|
if a.name != b.name ||
|
||||||
|
a.line != b.line ||
|
||||||
|
!reflect.DeepEqual(a.field, b.field) ||
|
||||||
|
len(a.children) != len(b.children) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i := 0; i < len(a.children); i++ {
|
||||||
|
if !nodesEqual(a.children[i], b.children[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user