Skip to content

Commit

Permalink
added tests and feature for minimal required fields only
Browse files Browse the repository at this point in the history
  • Loading branch information
Skarlso committed May 23, 2024
1 parent b38acf4 commit a686cc9
Show file tree
Hide file tree
Showing 7 changed files with 316 additions and 7,325 deletions.
10 changes: 6 additions & 4 deletions cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ var (
format string
stdOut bool
comments bool
minimal bool
)

func init() {
Expand All @@ -45,8 +46,9 @@ func init() {
f.StringVarP(&url, "url", "u", "", "If provided, will use this URL to fetch CRD YAML content from.")
f.StringVarP(&output, "output", "o", "", "The location of the output file. Default is next to the CRD.")
f.StringVarP(&format, "format", "f", FormatYAML, "The format in which to output. Default is YAML. Options are: yaml, html.")
f.BoolVarP(&stdOut, "stdout", "s", false, "If set, it will output the generated content to stdout")
f.BoolVarP(&comments, "comments", "m", false, "If set, it will add descriptions as comments to each line where available")
f.BoolVarP(&stdOut, "stdout", "s", false, "If set, it will output the generated content to stdout.")
f.BoolVarP(&comments, "comments", "m", false, "If set, it will add descriptions as comments to each line where available.")
f.BoolVarP(&minimal, "minimal", "l", false, "If set, only the minimal required example yaml is generated.")
}

func runGenerate(_ *cobra.Command, _ []string) error {
Expand Down Expand Up @@ -95,8 +97,8 @@ func runGenerate(_ *cobra.Command, _ []string) error {
return fmt.Errorf("failed to load templates: %w", err)
}

return pkg.RenderContent(w, content, comments)
return pkg.RenderContent(w, content, comments, minimal)
}

return pkg.Generate(crd, w, comments)
return pkg.Generate(crd, w, comments, minimal)
}
22 changes: 14 additions & 8 deletions pkg/create_html_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"html/template"
"io"
"io/fs"
"slices"
"sort"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
Expand Down Expand Up @@ -60,22 +61,22 @@ func LoadTemplates() error {
}

// RenderContent creates an HTML website from the CRD content.
func RenderContent(w io.Writer, crdContent []byte, comments bool) error {
func RenderContent(w io.Writer, crdContent []byte, comments, minimal bool) error {
crd := &v1beta1.CustomResourceDefinition{}
if err := yaml.Unmarshal(crdContent, crd); err != nil {
return fmt.Errorf("failed to unmarshal into custom resource definition: %w", err)
}
versions := make([]Version, 0)
parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, comments, true)
parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, comments, minimal)

for _, version := range crd.Spec.Versions {
out, err := parseCRD(version.Schema.OpenAPIV3Schema.Properties, version.Name, version.Schema.OpenAPIV3Schema.Required)
out, err := parseCRD(version.Schema.OpenAPIV3Schema.Properties, version.Name, minimal, rootRequiredFields)
if err != nil {
return fmt.Errorf("failed to parse properties: %w", err)
}
var buffer []byte
buf := bytes.NewBuffer(buffer)
if err := parser.ParseProperties(version.Name, buf, version.Schema.OpenAPIV3Schema.Properties); err != nil {
if err := parser.ParseProperties(version.Name, buf, version.Schema.OpenAPIV3Schema.Properties, rootRequiredFields); err != nil {
return fmt.Errorf("failed to generate yaml sample: %w", err)
}
versions = append(versions, Version{
Expand Down Expand Up @@ -115,7 +116,7 @@ type Property struct {

// parseCRD takes the properties and constructs a linked list out of the embedded properties that the recursive
// template can call and construct linked divs.
func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, requiredList []string) ([]*Property, error) {
func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, minimal bool, requiredList []string) ([]*Property, error) {
output := make([]*Property, 0, len(properties))
sortedKeys := make([]string, 0, len(properties))

Expand All @@ -125,6 +126,11 @@ func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, req
sort.Strings(sortedKeys)

for _, k := range sortedKeys {
if minimal {
if !slices.Contains(requiredList, k) {
continue
}
}
// Create the Property with the values necessary.
// Check if there are properties for it in Properties or in Array -> Properties.
// If yes, call parseCRD and add the result to the created properties Properties list.
Expand Down Expand Up @@ -155,21 +161,21 @@ func parseCRD(properties map[string]v1beta1.JSONSchemaProps, version string, req
switch {
case len(properties[k].Properties) > 0 && properties[k].AdditionalProperties == nil:
requiredList = v.Required
out, err := parseCRD(properties[k].Properties, version, requiredList)
out, err := parseCRD(properties[k].Properties, version, minimal, requiredList)
if err != nil {
return nil, err
}
p.Properties = out
case properties[k].Type == array && properties[k].Items.Schema != nil && len(properties[k].Items.Schema.Properties) > 0:
requiredList = v.Required
out, err := parseCRD(properties[k].Items.Schema.Properties, version, requiredList)
out, err := parseCRD(properties[k].Items.Schema.Properties, version, minimal, requiredList)
if err != nil {
return nil, err
}
p.Properties = out
case properties[k].AdditionalProperties != nil:
requiredList = v.Required
out, err := parseCRD(properties[k].AdditionalProperties.Schema.Properties, version, requiredList)
out, err := parseCRD(properties[k].AdditionalProperties.Schema.Properties, version, minimal, requiredList)
if err != nil {
return nil, err
}
Expand Down
25 changes: 18 additions & 7 deletions pkg/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"io"
"slices"
"sort"
"strings"

Expand All @@ -12,16 +13,18 @@ import (

const array = "array"

var rootRequiredFields = []string{"apiVersion", "kind", "spec"}

// Generate takes a CRD content and path, and outputs.
func Generate(crd *v1beta1.CustomResourceDefinition, w io.WriteCloser, enableComments bool) (err error) {
func Generate(crd *v1beta1.CustomResourceDefinition, w io.WriteCloser, enableComments, minimal bool) (err error) {
defer func() {
if cerr := w.Close(); cerr != nil {
err = errors.Join(err, cerr)
}
}()
parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, enableComments, false)
parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, enableComments, minimal)
for i, version := range crd.Spec.Versions {
if err := parser.ParseProperties(version.Name, w, version.Schema.OpenAPIV3Schema.Properties); err != nil {
if err := parser.ParseProperties(version.Name, w, version.Schema.OpenAPIV3Schema.Properties, rootRequiredFields); err != nil {
return fmt.Errorf("failed to parse properties: %w", err)
}

Expand Down Expand Up @@ -68,7 +71,7 @@ func NewParser(group, kind string, comments, requiredOnly bool) *Parser {
// ParseProperties takes a writer and puts out any information / properties it encounters during the runs.
// It will recursively parse every "properties:" and "additionalProperties:". Using the types, it will also output
// some sample data based on those types.
func (p *Parser) ParseProperties(version string, file io.Writer, properties map[string]v1beta1.JSONSchemaProps) error {
func (p *Parser) ParseProperties(version string, file io.Writer, properties map[string]v1beta1.JSONSchemaProps, requiredFields []string) error {
sortedKeys := make([]string, 0, len(properties))
for k := range properties {
sortedKeys = append(sortedKeys, k)
Expand All @@ -77,6 +80,13 @@ func (p *Parser) ParseProperties(version string, file io.Writer, properties map[

w := &writer{}
for _, k := range sortedKeys {
// if field is not required, skip the entire flow.
if p.onlyRequired {
if !slices.Contains(requiredFields, k) {
continue
}
}

if p.inArray {
w.write(file, k+":")
p.inArray = false
Expand Down Expand Up @@ -112,7 +122,7 @@ func (p *Parser) ParseProperties(version string, file io.Writer, properties map[
w.write(file, fmt.Sprintf("\n%s- ", strings.Repeat(" ", p.indent)))
p.indent += 2
p.inArray = true
if err := p.ParseProperties(version, file, properties[k].Items.Schema.Properties); err != nil {
if err := p.ParseProperties(version, file, properties[k].Items.Schema.Properties, properties[k].Items.Schema.Required); err != nil {
return err
}
p.indent -= 2
Expand All @@ -124,7 +134,7 @@ func (p *Parser) ParseProperties(version string, file io.Writer, properties map[
w.write(file, "\n")
// recursively parse all sub-properties
p.indent += 2
if err := p.ParseProperties(version, file, properties[k].Properties); err != nil {
if err := p.ParseProperties(version, file, properties[k].Properties, properties[k].Required); err != nil {
return err
}
p.indent -= 2
Expand All @@ -133,8 +143,9 @@ func (p *Parser) ParseProperties(version string, file io.Writer, properties map[
w.write(file, " {}\n")
} else {
w.write(file, "\n")

p.indent += 2
if err := p.ParseProperties(version, file, properties[k].AdditionalProperties.Schema.Properties); err != nil {
if err := p.ParseProperties(version, file, properties[k].AdditionalProperties.Schema.Properties, properties[k].AdditionalProperties.Schema.Required); err != nil {
return err
}
p.indent -= 2
Expand Down
8 changes: 4 additions & 4 deletions pkg/generate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ func TestGenerate(t *testing.T) {

version := crd.Spec.Versions[0]
parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, false, false)
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties))
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties, rootRequiredFields))

golden, err := os.ReadFile(filepath.Join("testdata", "sample_crd_golden.yaml"))
require.NoError(t, err)
Expand All @@ -44,7 +44,7 @@ func TestGenerateWithExample(t *testing.T) {

parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, false, false)
version := crd.Spec.Versions[0]
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties))
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties, rootRequiredFields))

golden, err := os.ReadFile(filepath.Join("testdata", "sample_crd_with_example_golden.yaml"))
require.NoError(t, err)
Expand All @@ -64,7 +64,7 @@ func TestGenerateWithComments(t *testing.T) {

parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, true, false)
version := crd.Spec.Versions[0]
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties))
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties, rootRequiredFields))

golden, err := os.ReadFile(filepath.Join("testdata", "sample_crd_with_comments_golden.yaml"))
require.NoError(t, err)
Expand All @@ -84,7 +84,7 @@ func TestGenerateMinimal(t *testing.T) {

parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, false, true)
version := crd.Spec.Versions[0]
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties))
require.NoError(t, parser.ParseProperties(version.Name, buffer, version.Schema.OpenAPIV3Schema.Properties, rootRequiredFields))

golden, err := os.ReadFile(filepath.Join("testdata", "sample_crd_with_minimal_example_golden.yaml"))
require.NoError(t, err)
Expand Down
2 changes: 1 addition & 1 deletion pkg/testdata/sample_crd_with_minimal_example_golden.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
apiVersion: delivery.krok.app/v1alpha1
kind: KrokCommand
spec:
image: "krok-hook/slack-notification:v0.0.1"
image: string
Loading

0 comments on commit a686cc9

Please sign in to comment.