From 8e6433a53f53b41e561f303bc933fd4e95f71d55 Mon Sep 17 00:00:00 2001 From: Ian Howell Date: Tue, 26 Feb 2019 15:28:23 -0600 Subject: [PATCH] Add functionality to generate a schema from a values.yaml Signed-off-by: Ian Howell --- pkg/chartutil/values.go | 67 ++++++++++++++++++++++++++++++++++++ pkg/chartutil/values_test.go | 43 +++++++++++++++++++++++ 2 files changed, 110 insertions(+) diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 50897c036..62d5f2dfc 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -203,6 +203,73 @@ func ReadSchematizedValues(data, schemaData []byte) (Values, error) { return values, nil } +// GenerateSchema will create a JSON Schema (in YAML format) for the given values +func GenerateSchema(values Values) Schema { + schema := Schema{ + "type": "object", + "title": "Values", + } + if len(values) > 0 { + schema["properties"] = parsePropertiesFromValues(values) + } + return schema +} + +func parsePropertiesFromValues(values Values) map[string]map[string]interface{} { + properties := make(map[string]map[string]interface{}) + for k, v := range values { + // If the value is null, then there's no way to determine the properties attributes + if v == nil || v == "" { + continue + } + + properties[k] = make(map[string]interface{}) + // the following types are the only types possible from unmarshalling + switch v.(type) { + case bool: + properties[k]["type"] = "bool" + case float64: + properties[k]["type"] = "number" + case string: + properties[k]["type"] = "string" + case []interface{}: + properties[k]["type"] = "array" + properties[k]["items"] = parseItemsFromValues(v.([]interface{})) + case map[string]interface{}: + properties[k]["type"] = "object" + object := parsePropertiesFromValues(v.(map[string]interface{})) + if len(object) > 0 { + properties[k]["object"] = object + } + } + } + return properties +} + +func parseItemsFromValues(items []interface{}) map[string]interface{} { + properties := make(map[string]interface{}) + v := items[0] + // the following types are the only types possible from unmarshalling + switch v.(type) { + case bool: + properties["type"] = "bool" + case float64: + properties["type"] = "number" + case string: + properties["type"] = "string" + case []interface{}: + properties["type"] = "array" + properties["items"] = parseItemsFromValues(v.([]interface{})) + case map[string]interface{}: + properties["type"] = "object" + object := parsePropertiesFromValues(v.(map[string]interface{})) + if len(object) > 0 { + properties["object"] = object + } + } + return properties +} + // CoalesceValues coalesces all of the values in a chart (and its subcharts). // // Values are coalesced together using the following rules: diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go index fbec5ae9d..18dcd82e5 100644 --- a/pkg/chartutil/values_test.go +++ b/pkg/chartutil/values_test.go @@ -435,6 +435,7 @@ func TestCoalesceTables(t *testing.T) { t.Errorf("Expected boat string, got %v", dst["boat"]) } } + func TestPathValue(t *testing.T) { doc := ` title: "Moby Dick" @@ -627,6 +628,48 @@ func matchSchema(t *testing.T, data Schema) { assertEqualProperty(t, property, expected, data) } +func TestGenerateSchema(t *testing.T) { + doc := `# Test YAML parse +firstname: John +middlename: null +lastname: Doe +age: 25 +likesCoffee: true +employmentInfo: + title: Software Developer + salary: 100000 +addresses: + - city: Springfield + street: Main + number: 12345 + - city: New York + street: Broadway + number: 67890 +phoneNumbers: + - "(888) 888-8888" + - "(555) 555-5555" +` + + values, _ := ReadValues([]byte(doc)) + schema := GenerateSchema(values) + assertEqualProperty(t, ".title", "Values", schema) + assertEqualProperty(t, ".type", "object", schema) + assertEqualProperty(t, ".properties.firstname.type", "string", schema) + assertEqualProperty(t, ".properties.lastname.type", "string", schema) + assertEqualProperty(t, ".properties.age.type", "number", schema) + assertEqualProperty(t, ".properties.likesCoffee.type", "bool", schema) + assertEqualProperty(t, ".properties.employmentInfo.type", "object", schema) + assertEqualProperty(t, ".properties.employmentInfo.object.title.type", "string", schema) + assertEqualProperty(t, ".properties.employmentInfo.object.salary.type", "number", schema) + assertEqualProperty(t, ".properties.addresses.type", "array", schema) + assertEqualProperty(t, ".properties.addresses.items.type", "object", schema) + assertEqualProperty(t, ".properties.addresses.items.object.street.type", "string", schema) + assertEqualProperty(t, ".properties.addresses.items.object.number.type", "number", schema) + assertEqualProperty(t, ".properties.addresses.items.object.city.type", "string", schema) + assertEqualProperty(t, ".properties.phoneNumbers.type", "array", schema) + assertEqualProperty(t, ".properties.phoneNumbers.items.type", "string", schema) +} + func assertEqualProperty(t *testing.T, property, expected string, data map[string]interface{}) { if o, err := ttpl("{{"+property+"}}", data); err != nil { t.Errorf("%s: %s", property, err)