1
0
mirror of https://github.com/rancher/os.git synced 2025-09-02 15:24:32 +00:00

Add command to validate configuration

This commit is contained in:
Josh Curl
2016-11-06 13:41:46 -08:00
parent 33b6145162
commit 25e5ca5e4c
7 changed files with 534 additions and 12 deletions

View File

@@ -76,6 +76,17 @@ func configSubcommands() []cli.Command {
},
},
},
{
Name: "validate",
Usage: "validate configuration from stdin",
Action: validate,
Flags: []cli.Flag{
cli.StringFlag{
Name: "input, i",
Usage: "File from which to read",
},
},
},
}
}
@@ -183,18 +194,7 @@ func configGet(c *cli.Context) error {
}
func merge(c *cli.Context) error {
input := os.Stdin
inputFile := c.String("input")
if inputFile != "" {
var err error
input, err = os.Open(inputFile)
if err != nil {
log.Fatal(err)
}
defer input.Close()
}
bytes, err := ioutil.ReadAll(input)
bytes, err := inputBytes(c)
if err != nil {
log.Fatal(err)
}
@@ -224,3 +224,32 @@ func export(c *cli.Context) error {
return nil
}
func validate(c *cli.Context) error {
bytes, err := inputBytes(c)
if err != nil {
log.Fatal(err)
}
validationErrors, err := config.Validate(bytes)
if err != nil {
log.Fatal(err)
}
for _, validationError := range validationErrors.Errors() {
log.Error(validationError)
}
return nil
}
func inputBytes(c *cli.Context) ([]byte, error) {
input := os.Stdin
inputFile := c.String("input")
if inputFile != "" {
var err error
input, err = os.Open(inputFile)
if err != nil {
return nil, err
}
defer input.Close()
}
return ioutil.ReadAll(input)
}

195
config/schema.go Normal file
View File

@@ -0,0 +1,195 @@
package config
var schema = `{
"type": "object",
"additionalProperties": false,
"properties": {
"ssh_authorized_keys": {"$ref": "#/definitions/list_of_strings"},
"write_files": {
"type": "array",
"items": {"$ref": "#/definitions/file_config"}
},
"hostname": {"type": "string"},
"mounts": {"type": "array"},
"rancher": {"$ref": "#/definitions/rancher_config"},
"runcmd": {"type": "array"}
},
"definitions": {
"rancher_config": {
"id": "#/definitions/rancher_config",
"type": "object",
"additionalProperties": false,
"properties": {
"console": {"type": "string"},
"environment": {"type": "object"},
"services": {"type": "object"},
"bootstrap_containers": {"type": "object"},
"autoformat": {"type": "object"},
"bootstrap_docker": {"$ref": "#/definitions/docker_config"},
"cloud_init": {"$ref": "#/definitions/cloud_init_config"},
"debug": {"type": "boolean"},
"rm_usr": {"type": "boolean"},
"no_sharedroot": {"type": "boolean"},
"log": {"type": "boolean"},
"force_console_rebuild": {"type": "boolean"},
"disable": {"$ref": "#/definitions/list_of_strings"},
"services_include": {"type": "object"},
"modules": {"$ref": "#/definitions/list_of_strings"},
"network": {"$ref": "#/definitions/network_config"},
"default_network": {"type": "object"},
"repositories": {"type": "object"},
"ssh": {"$ref": "#/definitions/ssh_config"},
"state": {"$ref": "#/definitions/state_config"},
"system_docker": {"$ref": "#/definitions/docker_config"},
"upgrade": {"$ref": "#/definitions/upgrade_config"},
"docker": {"$ref": "#/definitions/docker_config"},
"registry_auths": {"type": "object"},
"defaults": {"$ref": "#/definitions/defaults_config"},
"resize_device": {"type": "string"},
"sysctl": {"type": "object"}
}
},
"file_config": {
"id": "#/definitions/file_config",
"type": "object",
"additionalProperties": false,
"properties": {
"encoding": {"type": "string"},
"content": {"type": "string"},
"owner": {"type": "string"},
"path": {"type": "string"},
"permissions": {"type": "string"}
}
},
"network_config": {
"id": "#/definitions/network_config",
"type": "object",
"additionalProperties": false,
"properties": {
"pre_cmds": {"$ref": "#/definitions/list_of_strings"},
"dns": {"type": "object"},
"interfaces": {"type": "object"},
"post_cmds": {"$ref": "#/definitions/list_of_strings"},
"http_proxy": {"type": "string"},
"https_proxy": {"type": "string"},
"no_proxy": {"type": "string"}
}
},
"upgrade_config": {
"id": "#/definitions/upgrade_config",
"type": "object",
"additionalProperties": false,
"properties": {
"url": {"type": "string"},
"image": {"type": "string"},
"rollback": {"type": "string"}
}
},
"docker_config": {
"id": "#/definitions/docker_config",
"type": "object",
"additionalProperties": false,
"properties": {
"engine": {"type": "string"},
"tls": {"type": "boolean"},
"tls_args": {"$ref": "#/definitions/list_of_strings"},
"args": {"$ref": "#/definitions/list_of_strings"},
"extra_args": {"$ref": "#/definitions/list_of_strings"},
"server_cert": {"type": "string"},
"server_key": {"type": "string"},
"ca_cert": {"type": "string"},
"ca_key": {"type": "string"},
"environment": {"$ref": "#/definitions/list_of_strings"},
"storage_context": {"type": "string"},
"exec": {"type": "boolean"},
"bridge": {"type": "string"},
"config_file": {"type": "string"},
"containerd": {"type": "string"},
"debug": {"type": "boolean"},
"exec_root": {"type": "string"},
"group": {"type": "string"},
"graph": {"type": "string"},
"host": {"type": "string"},
"live_restore": {"type": "boolean"},
"log_driver": {"type": "string"},
"log_opts": {"type": "object"},
"pid_file": {"type": "string"},
"registry_mirror": {"type": "string"},
"restart": {"type": "boolean"},
"selinux_enabled": {"type": "boolean"},
"storage_driver": {"type": "string"},
"userland_proxy": {"type": "boolean"}
}
},
"ssh_config": {
"id": "#/definitions/ssh_config",
"type": "object",
"additionalProperties": false,
"properties": {
"keys": {"type": "object"}
}
},
"state_config": {
"id": "#/definitions/state_config",
"type": "object",
"additionalProperties": false,
"properties": {
"directory": {"type": "string"},
"fstype": {"type": "string"},
"dev": {"type": "string"},
"wait": {"type": "boolean"},
"required": {"type": "boolean"},
"autoformat": {"$ref": "#/definitions/list_of_strings"},
"mdadm_scan": {"type": "boolean"},
"script": {"type": "string"},
"oem_fstype": {"type": "string"},
"oem_dev": {"type": "string"}
}
},
"cloud_init_config": {
"id": "#/definitions/cloud_init_config",
"type": "object",
"additionalProperties": false,
"properties": {
"datasources": {"$ref": "#/definitions/list_of_strings"}
}
},
"defaults_config": {
"id": "#/definitions/defaults_config",
"type": "object",
"additionalProperties": false,
"properties": {
"hostname": {"type": "string"},
"docker": {"type": "object"},
"network": {"$ref": "#/definitions/network_config"}
}
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
}
}
}
`

42
config/validate.go Normal file
View File

@@ -0,0 +1,42 @@
package config
import (
yaml "github.com/cloudfoundry-incubator/candiedyaml"
"github.com/xeipuuv/gojsonschema"
)
// TODO: use this function from libcompose
func convertKeysToStrings(item interface{}) interface{} {
switch typedDatas := item.(type) {
case map[string]interface{}:
for key, value := range typedDatas {
typedDatas[key] = convertKeysToStrings(value)
}
return typedDatas
case map[interface{}]interface{}:
newMap := make(map[string]interface{})
for key, value := range typedDatas {
stringKey := key.(string)
newMap[stringKey] = convertKeysToStrings(value)
}
return newMap
case []interface{}:
for i, value := range typedDatas {
typedDatas[i] = append(typedDatas, convertKeysToStrings(value))
}
return typedDatas
default:
return item
}
}
func Validate(bytes []byte) (*gojsonschema.Result, error) {
var rawCfg map[string]interface{}
if err := yaml.Unmarshal([]byte(bytes), &rawCfg); err != nil {
return nil, err
}
rawCfg = convertKeysToStrings(rawCfg).(map[string]interface{})
loader := gojsonschema.NewGoLoader(rawCfg)
schemaLoader := gojsonschema.NewStringLoader(schema)
return gojsonschema.Validate(schemaLoader, loader)
}

29
config/validate_test.go Normal file
View File

@@ -0,0 +1,29 @@
package config
import (
"fmt"
"strings"
"testing"
)
func testValidate(t *testing.T, cfg []byte, contains string) {
validationErrors, err := Validate(cfg)
if err != nil {
t.Fatal(err)
}
if contains == "" && len(validationErrors.Errors()) != 0 {
t.Fail()
}
if !strings.Contains(fmt.Sprint(validationErrors.Errors()), contains) {
t.Fail()
}
}
func TestValidate(t *testing.T) {
testValidate(t, []byte("{}"), "")
testValidate(t, []byte(`rancher:
log: true
`), "")
testValidate(t, []byte("bad_key: {}"), "Additional property bad_key is not allowed")
testValidate(t, []byte("rancher: []"), "rancher: Invalid type. Expected: object, given: array")
}

32
scripts/inline_schema.go Normal file
View File

@@ -0,0 +1,32 @@
package main
import (
"io/ioutil"
"os"
"text/template"
)
func main() {
t, err := template.New("schema_template").ParseFiles("./scripts/schema_template")
if err != nil {
panic(err)
}
schema, err := ioutil.ReadFile("./scripts/schema.json")
if err != nil {
panic(err)
}
inlinedFile, err := os.Create("config/schema.go")
if err != nil {
panic(err)
}
err = t.Execute(inlinedFile, map[string]string{
"schema": string(schema),
})
if err != nil {
panic(err)
}
}

192
scripts/schema.json Normal file
View File

@@ -0,0 +1,192 @@
{
"type": "object",
"additionalProperties": false,
"properties": {
"ssh_authorized_keys": {"$ref": "#/definitions/list_of_strings"},
"write_files": {
"type": "array",
"items": {"$ref": "#/definitions/file_config"}
},
"hostname": {"type": "string"},
"mounts": {"type": "array"},
"rancher": {"$ref": "#/definitions/rancher_config"},
"runcmd": {"type": "array"}
},
"definitions": {
"rancher_config": {
"id": "#/definitions/rancher_config",
"type": "object",
"additionalProperties": false,
"properties": {
"console": {"type": "string"},
"environment": {"type": "object"},
"services": {"type": "object"},
"bootstrap_containers": {"type": "object"},
"autoformat": {"type": "object"},
"bootstrap_docker": {"$ref": "#/definitions/docker_config"},
"cloud_init": {"$ref": "#/definitions/cloud_init_config"},
"debug": {"type": "boolean"},
"rm_usr": {"type": "boolean"},
"no_sharedroot": {"type": "boolean"},
"log": {"type": "boolean"},
"force_console_rebuild": {"type": "boolean"},
"disable": {"$ref": "#/definitions/list_of_strings"},
"services_include": {"type": "object"},
"modules": {"$ref": "#/definitions/list_of_strings"},
"network": {"$ref": "#/definitions/network_config"},
"default_network": {"type": "object"},
"repositories": {"type": "object"},
"ssh": {"$ref": "#/definitions/ssh_config"},
"state": {"$ref": "#/definitions/state_config"},
"system_docker": {"$ref": "#/definitions/docker_config"},
"upgrade": {"$ref": "#/definitions/upgrade_config"},
"docker": {"$ref": "#/definitions/docker_config"},
"registry_auths": {"type": "object"},
"defaults": {"$ref": "#/definitions/defaults_config"},
"resize_device": {"type": "string"},
"sysctl": {"type": "object"}
}
},
"file_config": {
"id": "#/definitions/file_config",
"type": "object",
"additionalProperties": false,
"properties": {
"encoding": {"type": "string"},
"content": {"type": "string"},
"owner": {"type": "string"},
"path": {"type": "string"},
"permissions": {"type": "string"}
}
},
"network_config": {
"id": "#/definitions/network_config",
"type": "object",
"additionalProperties": false,
"properties": {
"pre_cmds": {"$ref": "#/definitions/list_of_strings"},
"dns": {"type": "object"},
"interfaces": {"type": "object"},
"post_cmds": {"$ref": "#/definitions/list_of_strings"},
"http_proxy": {"type": "string"},
"https_proxy": {"type": "string"},
"no_proxy": {"type": "string"}
}
},
"upgrade_config": {
"id": "#/definitions/upgrade_config",
"type": "object",
"additionalProperties": false,
"properties": {
"url": {"type": "string"},
"image": {"type": "string"},
"rollback": {"type": "string"}
}
},
"docker_config": {
"id": "#/definitions/docker_config",
"type": "object",
"additionalProperties": false,
"properties": {
"engine": {"type": "string"},
"tls": {"type": "boolean"},
"tls_args": {"$ref": "#/definitions/list_of_strings"},
"args": {"$ref": "#/definitions/list_of_strings"},
"extra_args": {"$ref": "#/definitions/list_of_strings"},
"server_cert": {"type": "string"},
"server_key": {"type": "string"},
"ca_cert": {"type": "string"},
"ca_key": {"type": "string"},
"environment": {"$ref": "#/definitions/list_of_strings"},
"storage_context": {"type": "string"},
"exec": {"type": "boolean"},
"bridge": {"type": "string"},
"config_file": {"type": "string"},
"containerd": {"type": "string"},
"debug": {"type": "boolean"},
"exec_root": {"type": "string"},
"group": {"type": "string"},
"graph": {"type": "string"},
"host": {"type": "string"},
"live_restore": {"type": "boolean"},
"log_driver": {"type": "string"},
"log_opts": {"type": "object"},
"pid_file": {"type": "string"},
"registry_mirror": {"type": "string"},
"restart": {"type": "boolean"},
"selinux_enabled": {"type": "boolean"},
"storage_driver": {"type": "string"},
"userland_proxy": {"type": "boolean"}
}
},
"ssh_config": {
"id": "#/definitions/ssh_config",
"type": "object",
"additionalProperties": false,
"properties": {
"keys": {"type": "object"}
}
},
"state_config": {
"id": "#/definitions/state_config",
"type": "object",
"additionalProperties": false,
"properties": {
"directory": {"type": "string"},
"fstype": {"type": "string"},
"dev": {"type": "string"},
"wait": {"type": "boolean"},
"required": {"type": "boolean"},
"autoformat": {"$ref": "#/definitions/list_of_strings"},
"mdadm_scan": {"type": "boolean"},
"script": {"type": "string"},
"oem_fstype": {"type": "string"},
"oem_dev": {"type": "string"}
}
},
"cloud_init_config": {
"id": "#/definitions/cloud_init_config",
"type": "object",
"additionalProperties": false,
"properties": {
"datasources": {"$ref": "#/definitions/list_of_strings"}
}
},
"defaults_config": {
"id": "#/definitions/defaults_config",
"type": "object",
"additionalProperties": false,
"properties": {
"hostname": {"type": "string"},
"docker": {"type": "object"},
"network": {"$ref": "#/definitions/network_config"}
}
},
"list_of_strings": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
}
}
}

3
scripts/schema_template Normal file
View File

@@ -0,0 +1,3 @@
package config
var schema = `{{.schema}}`