diff --git a/pkg/chartutil/values.go b/pkg/chartutil/values.go index 644cdd49b..7d47d00a7 100644 --- a/pkg/chartutil/values.go +++ b/pkg/chartutil/values.go @@ -45,6 +45,25 @@ const GlobalKey = "global" // Values represents a collection of chart values. type Values map[string]interface{} +// SchemaProperties represents the nested objects of a schema +type SchemaProperties map[string]*Schema + +// Schema is a JSON schema which can be applied to a values file to validate it +type Schema struct { + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + Type string `json:"type,omitempty"` + Properties SchemaProperties `json:"properties,omitempty"` + Required []string `json:"required,omitempty"` + Minimum int `json:"minimum,omitempty"` +} + +// YAML encodes the Values into a YAML string. +func (s Schema) YAML() (string, error) { + b, err := yaml.Marshal(s) + return string(b), err +} + // YAML encodes the Values into a YAML string. func (v Values) YAML() (string, error) { b, err := yaml.Marshal(v) @@ -124,6 +143,12 @@ func ReadValues(data []byte) (vals Values, err error) { return vals, err } +// ReadSchema will parse YAML byte data into a Schema. +func ReadSchema(data []byte) (schema Schema, err error) { + err = yaml.Unmarshal(data, &schema) + return schema, err +} + // ReadValuesFile will parse a YAML file into a map of values. func ReadValuesFile(filename string) (Values, error) { data, err := ioutil.ReadFile(filename) diff --git a/pkg/chartutil/values_test.go b/pkg/chartutil/values_test.go index 2faaf7828..2fe6326f1 100644 --- a/pkg/chartutil/values_test.go +++ b/pkg/chartutil/values_test.go @@ -435,3 +435,118 @@ chapter: } } } + +func TestReadSchema(t *testing.T) { + schemaTest := `# Test YAML parse +title: Values +type: object +properties: + name: + description: Service name + type: string + protocol: + type: string + port: + description: Port + type: integer + minimum: 0 + image: + description: Container Image + type: object + properties: + repo: + type: string + tag: + type: string +required: + - protocol + - port +` + data, err := ReadSchema([]byte(schemaTest)) + if err != nil { + t.Fatalf("Error parsing bytes: %s", err) + } + matchSchema(t, data) +} + +func matchSchema(t *testing.T, data Schema) { + if data.Title != "Values" { + t.Errorf("Expected .title to be 'Values', got '%s'", data.Title) + } + + if data.Type != "object" { + t.Errorf("Expected .type to be 'object', got '%s'", data.Type) + } + + if name, ok := data.Properties["name"]; !ok { + t.Errorf("Expected property '.properties.name' is missing") + } else { + if name.Description != "Service name" { + t.Errorf("Expected .properties.name.description to be 'Service name', got '%s'", name.Description) + } + if name.Type != "string" { + t.Errorf("Expected .properties.name.type to be 'string', got '%s'", name.Description) + } + } + + if protocol, ok := data.Properties["protocol"]; !ok { + t.Errorf("Expected property '.properties.protocol' is missing") + } else { + if protocol.Type != "string" { + t.Errorf("Expected .properties.protocol.type to be 'string', got '%s'", protocol.Description) + } + } + + if port, ok := data.Properties["port"]; !ok { + t.Errorf("Expected property '.properties.port' is missing") + } else { + if port.Description != "Port" { + t.Errorf("Expected .properties.port.description to be 'Port', got '%s'", port.Description) + } + if port.Type != "integer" { + t.Errorf("Expected .properties.port.type to be 'string', got '%s'", port.Description) + } + if port.Minimum != 0 { + t.Errorf("Expected .properties.port.minimum to be 0, got %d", port.Minimum) + } + } + + if image, ok := data.Properties["image"]; !ok { + t.Errorf("Expected property '.properties.image' is missing") + } else { + if image.Description != "Container Image" { + t.Errorf("Expected .properties.image.description to be 'Container Image', got '%s'", image.Description) + } + if image.Type != "object" { + t.Errorf("Expected .properties.image.type to be 'object', got '%s'", image.Description) + } + if repo, ok := image.Properties["repo"]; !ok { + t.Errorf("Expected property '.properties.repo' is missing") + } else { + if repo.Type != "string" { + t.Errorf("Expected .properties.repo.type to be 'string', got '%s'", repo.Description) + } + } + if tag, ok := image.Properties["tag"]; !ok { + t.Errorf("Expected property '.properties.tag' is missing") + } else { + if tag.Type != "string" { + t.Errorf("Expected .properties.tag.type to be 'string', got '%s'", tag.Description) + } + } + } + + if len(data.Required) != 2 { + t.Errorf("Expected length of .required to be 2, got %d", len(data.Required)) + } + + expectedRequired := []string{ + "protocol", + "port", + } + for i := 0; i < 2; i++ { + if data.Required[i] != expectedRequired[i] { + t.Errorf("Expected .required to be %v, got %v", expectedRequired, data.Required) + } + } +}