-
-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: generate an HTML based output for a given CRD when format is de…
…fined (#66) * feat: generate an HTML based output for a given CRD when format is defined * remove lint failure
- Loading branch information
Showing
6 changed files
with
7,696 additions
and
93 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,181 @@ | ||
package pkg | ||
|
||
import ( | ||
"bytes" | ||
"embed" | ||
"fmt" | ||
"html/template" | ||
"io" | ||
"io/fs" | ||
"sort" | ||
|
||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" | ||
"k8s.io/apimachinery/pkg/util/yaml" | ||
) | ||
|
||
// Version wraps a top level version resource which contains the underlying openAPIV3Schema. | ||
type Version struct { | ||
Version string | ||
Kind string | ||
Group string | ||
Properties []*Property | ||
Description string | ||
YAML string | ||
} | ||
|
||
// ViewPage is the template for view.html. | ||
type ViewPage struct { | ||
Versions []Version | ||
} | ||
|
||
var ( | ||
//go:embed templates | ||
files embed.FS | ||
templates map[string]*template.Template | ||
) | ||
|
||
// LoadTemplates creates a map of loaded templates that are primed and ready to be rendered. | ||
func LoadTemplates() error { | ||
if templates == nil { | ||
templates = make(map[string]*template.Template) | ||
} | ||
tmplFiles, err := fs.ReadDir(files, "templates") | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, tmpl := range tmplFiles { | ||
if tmpl.IsDir() { | ||
continue | ||
} | ||
pt, err := template.ParseFS(files, "templates/"+tmpl.Name()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
templates[tmpl.Name()] = pt | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// RenderContent creates an HTML website from the CRD content. | ||
func RenderContent(w io.Writer, crdContent []byte, comments 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) | ||
for _, version := range crd.Spec.Versions { | ||
out, err := parseCRD(version.Schema.OpenAPIV3Schema.Properties, version.Name, version.Schema.OpenAPIV3Schema.Required) | ||
if err != nil { | ||
return fmt.Errorf("failed to parse properties: %w", err) | ||
} | ||
var buffer []byte | ||
buf := bytes.NewBuffer(buffer) | ||
if err := ParseProperties(crd.Spec.Group, version.Name, crd.Spec.Names.Kind, version.Schema.OpenAPIV3Schema.Properties, buf, 0, false, comments); err != nil { | ||
return fmt.Errorf("failed to generate yaml sample: %w", err) | ||
} | ||
versions = append(versions, Version{ | ||
Version: version.Name, | ||
Properties: out, | ||
Kind: crd.Spec.Names.Kind, | ||
Group: crd.Spec.Group, | ||
Description: version.Schema.OpenAPIV3Schema.Description, | ||
YAML: buf.String(), | ||
}) | ||
} | ||
view := ViewPage{ | ||
Versions: versions, | ||
} | ||
t := templates["view.html"] | ||
if err := t.Execute(w, view); err != nil { | ||
return fmt.Errorf("failed to execute template: %w", err) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Property builds up a Tree structure of embedded things. | ||
type Property struct { | ||
Name string | ||
Description string | ||
Type string | ||
Nullable bool | ||
Patterns string | ||
Format string | ||
Indent int | ||
Version string | ||
Default string | ||
Required bool | ||
Properties []*Property | ||
} | ||
|
||
// 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) { | ||
output := make([]*Property, 0, len(properties)) | ||
sortedKeys := make([]string, 0, len(properties)) | ||
|
||
for k := range properties { | ||
sortedKeys = append(sortedKeys, k) | ||
} | ||
sort.Strings(sortedKeys) | ||
|
||
for _, k := range sortedKeys { | ||
// 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. | ||
// If not, or if we are done, add this new property to the list of properties and return it. | ||
v := properties[k] | ||
required := false | ||
for _, item := range requiredList { | ||
if item == k { | ||
required = true | ||
|
||
break | ||
} | ||
} | ||
p := &Property{ | ||
Name: k, | ||
Type: v.Type, | ||
Description: v.Description, | ||
Patterns: v.Pattern, | ||
Format: v.Format, | ||
Nullable: v.Nullable, | ||
Version: version, | ||
Required: required, | ||
} | ||
if v.Default != nil { | ||
p.Default = string(v.Default.Raw) | ||
} | ||
|
||
switch { | ||
case len(properties[k].Properties) > 0 && properties[k].AdditionalProperties == nil: | ||
requiredList = v.Required | ||
out, err := parseCRD(properties[k].Properties, version, 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) | ||
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) | ||
if err != nil { | ||
return nil, err | ||
} | ||
p.Properties = out | ||
} | ||
|
||
output = append(output, p) | ||
} | ||
|
||
return output, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,15 +6,34 @@ | |
rel="stylesheet" | ||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-twilight.min.css" | ||
/> | ||
<link | ||
rel="stylesheet" | ||
href="https://cdn.jsdelivr.net/npm/[email protected]/css/halfmoon.min.css" | ||
/> | ||
|
||
<title>Preview CRDs</title> | ||
<meta charset="utf-8"> | ||
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport"> | ||
<meta name="viewport" content="width=device-width, initial-scale=1"> | ||
<link href="static/css/main.css" rel="stylesheet" type="text/css"> | ||
<link href="static/css/prism.css" rel="stylesheet" type="text/css"> | ||
<link href="static/css/prism-okaidia.css" rel="stylesheet" type="text/css"> | ||
<link href="static/css/root.css" rel="stylesheet" type="text/css"> | ||
<link href="static/css/halfmoon-variables.min.css" rel="stylesheet" type="text/css"> | ||
<style> | ||
@media (max-width: 576px) { | ||
body .content-wrapper > div { | ||
padding-left: 1.5rem; | ||
padding-right: 1.5rem; | ||
} | ||
} | ||
|
||
body .content-wrapper { | ||
padding-bottom: 2rem; | ||
} | ||
</style> | ||
<style> | ||
|
||
</style> | ||
</head> | ||
|
||
<body class="dark-mode" data-dm-shortcut-enabled="true" data-sidebar-shortcut-enabled="true"> | ||
|
@@ -88,10 +107,6 @@ <h1> | |
console.log("todo: loop through all elements and collapse them") | ||
} | ||
</script> | ||
<script src="static/js/prism.js"> | ||
</script> | ||
<script src="static/js/clipboard.min.js"> | ||
</script> | ||
</body> | ||
</html> | ||
|
||
|
Oops, something went wrong.