mirror of
https://github.com/outbackdingo/matchbox.git
synced 2026-01-27 10:19:35 +00:00
api: Add Groups and GroupConfig with Requirements and Labels
* Add Group definitions to associate attribute matchers to particular Spec specifications to supercede use of machine.json files
This commit is contained in:
38
api/groups.go
Normal file
38
api/groups.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// GroupConfig parser errors
|
||||
var (
|
||||
ErrInvalidVersion = errors.New("api: mismatched API version")
|
||||
)
|
||||
|
||||
// Group associates matcher conditions with a Specification identifier.
|
||||
type Group struct {
|
||||
// Human readable name (optional)
|
||||
Name string `yaml:"name"`
|
||||
// Spec identifier
|
||||
Specification string `yaml:"spec"`
|
||||
// matcher conditions
|
||||
Matcher RequirementSet `yaml:"require"`
|
||||
}
|
||||
|
||||
// GroupConfig define an group import structure.
|
||||
type GroupConfig struct {
|
||||
APIVersion string `yaml:"api_version"`
|
||||
Groups []Group `yaml:"groups"`
|
||||
}
|
||||
|
||||
// ParseGroupConfig parses a YAML group config and returns a GroupConfig.
|
||||
func ParseGroupConfig(data []byte) (*GroupConfig, error) {
|
||||
config := new(GroupConfig)
|
||||
err := yaml.Unmarshal(data, config)
|
||||
if err == nil && config.APIVersion != APIVersion {
|
||||
return nil, ErrInvalidVersion
|
||||
}
|
||||
return config, err
|
||||
}
|
||||
47
api/groups_test.go
Normal file
47
api/groups_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseGroupConfig(t *testing.T) {
|
||||
validData := `
|
||||
api_version: v1alpha1
|
||||
groups:
|
||||
- name: node1
|
||||
spec: worker-central
|
||||
require:
|
||||
role: worker
|
||||
region: us-central1-a
|
||||
`
|
||||
validConfig := &GroupConfig{
|
||||
APIVersion: "v1alpha1",
|
||||
Groups: []Group{
|
||||
Group{
|
||||
Name: "node1",
|
||||
Specification: "worker-central",
|
||||
Matcher: RequirementSet(map[string]string{
|
||||
"role": "worker",
|
||||
"region": "us-central1-a",
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
wrongVersion := `api_version:`
|
||||
|
||||
cases := []struct {
|
||||
data string
|
||||
expectedConfig *GroupConfig
|
||||
expectedErr error
|
||||
}{
|
||||
{validData, validConfig, nil},
|
||||
{wrongVersion, nil, ErrInvalidVersion},
|
||||
}
|
||||
for _, c := range cases {
|
||||
config, err := ParseGroupConfig([]byte(c.data))
|
||||
assert.Equal(t, c.expectedConfig, config)
|
||||
assert.Equal(t, c.expectedErr, err)
|
||||
}
|
||||
}
|
||||
30
api/matchers.go
Normal file
30
api/matchers.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package api
|
||||
|
||||
// RequirementSet is a map of key:value equality requirements which
|
||||
// match against any Labels which are supersets.
|
||||
type RequirementSet map[string]string
|
||||
|
||||
// Matches returns true if the given labels satisfy all the requirements,
|
||||
// false otherwise.
|
||||
func (r RequirementSet) Matches(labels Labels) bool {
|
||||
for k, v := range r {
|
||||
if labels.Get(k) != v {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Labels present key to value mappings, independent of their storage.
|
||||
type Labels interface {
|
||||
// Get returns the value for the given label.
|
||||
Get(label string) string
|
||||
}
|
||||
|
||||
// LabelSet is a map of key:value labels.
|
||||
type LabelSet map[string]string
|
||||
|
||||
// Get returns the value for the given label.
|
||||
func (ls LabelSet) Get(label string) string {
|
||||
return ls[label]
|
||||
}
|
||||
53
api/matchers_test.go
Normal file
53
api/matchers_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRequirementMatches(t *testing.T) {
|
||||
// requirements
|
||||
reqs := map[string]string{
|
||||
"region": "Central US",
|
||||
"zone": "us-central1-a",
|
||||
"lot": "42",
|
||||
}
|
||||
attrs := map[string]string{
|
||||
"uuid": "16e7d8a7-bfa9-428b-9117-363341bb330b",
|
||||
}
|
||||
// labels
|
||||
labels := map[string]string{
|
||||
"region": "Central US",
|
||||
"zone": "us-central1-a",
|
||||
"lot": "42",
|
||||
}
|
||||
query := map[string]string{
|
||||
"uuid": "16e7d8a7-bfa9-428b-9117-363341bb330b",
|
||||
}
|
||||
lacking := map[string]string{
|
||||
"region": "Central US",
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
reqs map[string]string
|
||||
labels map[string]string
|
||||
expected bool
|
||||
}{
|
||||
{reqs, labels, true},
|
||||
{attrs, query, true},
|
||||
{reqs, lacking, false},
|
||||
// zero requirements match any label set
|
||||
{map[string]string{}, labels, true},
|
||||
}
|
||||
for _, c := range cases {
|
||||
r := RequirementSet(c.reqs)
|
||||
l := LabelSet(c.labels)
|
||||
assert.Equal(t, c.expected, r.Matches(l))
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelSetGet(t *testing.T) {
|
||||
labels := LabelSet(map[string]string{"a": "b"})
|
||||
assert.Equal(t, "b", labels.Get("a"))
|
||||
}
|
||||
@@ -6,6 +6,11 @@ import (
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
const (
|
||||
// APIVersion of the api server and its config types.
|
||||
APIVersion = "v1alpha1"
|
||||
)
|
||||
|
||||
var log = capnslog.NewPackageLogger("github.com/coreos/coreos-baremetal", "api")
|
||||
|
||||
// Config configures the api Server.
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@@ -17,6 +18,7 @@ var log = capnslog.NewPackageLogger("github.com/coreos/coreos-baremetal/cmd/boot
|
||||
func main() {
|
||||
flags := flag.NewFlagSet("bootcfg", flag.ExitOnError)
|
||||
address := flags.String("address", "127.0.0.1:8080", "HTTP listen address")
|
||||
configPath := flags.String("config", "", "Path to config file")
|
||||
dataPath := flags.String("data-path", "./data", "Path to data directory")
|
||||
imagesPath := flags.String("images-path", "./images", "Path to static assets")
|
||||
// available log levels https://godoc.org/github.com/coreos/pkg/capnslog#LogLevel
|
||||
@@ -44,13 +46,28 @@ func main() {
|
||||
// logging setup
|
||||
lvl, err := capnslog.ParseLevel(strings.ToUpper(*logLevel))
|
||||
if err != nil {
|
||||
log.Fatalf("Invalid log-level: %s", err.Error())
|
||||
log.Fatalf("Invalid log-level: %v", err.Error())
|
||||
}
|
||||
capnslog.SetGlobalLogLevel(lvl)
|
||||
capnslog.SetFormatter(capnslog.NewPrettyFormatter(os.Stdout, false))
|
||||
|
||||
// storage
|
||||
store := api.NewFileStore(http.Dir(*dataPath))
|
||||
|
||||
// bootstrap a group config
|
||||
if *configPath != "" {
|
||||
data, err := ioutil.ReadFile(*configPath)
|
||||
if err != nil {
|
||||
log.Fatalf("error reading config file: %v", err)
|
||||
}
|
||||
_, err = api.ParseGroupConfig(data)
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing group config: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
config := &api.Config{
|
||||
Store: api.NewFileStore(http.Dir(*dataPath)),
|
||||
Store: store,
|
||||
ImagePath: *imagesPath,
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user