Skip to content

Commit

Permalink
feat: add generating samples for any kind of object that supports ope…
Browse files Browse the repository at this point in the history
…nAPIV3schema
  • Loading branch information
Skarlso committed Oct 14, 2024
1 parent 1927b6b commit c884d59
Show file tree
Hide file tree
Showing 27 changed files with 2,006 additions and 164 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/sync_jsonschema.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Sync File from Repo B

on:
schedule:
- cron: "0 0 * * *" # Runs daily at midnight
workflow_dispatch:

jobs:
sync-file:
runs-on: ubuntu-latest
steps:
- name: Checkout Kubernetes API server
uses: actions/checkout@v4
with:
repository: kubernetes/apiextensions-apiserver
path: apiextensions-apiserver
- name: Checkout This Repository
uses: actions/checkout@v4
with:
repository: ${{ github.repository }}
path: this

- name: Install GitHub CLI
run: sudo apt-get install gh

- name: Run sync script
run: |
./this/hack/sync-file.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4 changes: 4 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,10 @@ linters-settings:
exclude-generated: true

issues:
exclude-files:
- pkg/types_jsonschema.go
- pkg/marshal.go
- pkg/marshal_test.go
exclude:
- composites
exclude-rules:
Expand Down
5 changes: 2 additions & 3 deletions cmd/crd.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import (
"path/filepath"

"github.com/spf13/cobra"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"

"github.com/Skarlso/crd-to-sample-yaml/pkg"
)
Expand Down Expand Up @@ -37,7 +36,7 @@ type crdGenArgs struct {
var crdArgs = &crdGenArgs{}

type Handler interface {
CRDs() ([]*v1beta1.CustomResourceDefinition, error)
CRDs() ([]*pkg.SchemaType, error)
}

func init() {
Expand Down Expand Up @@ -104,7 +103,7 @@ func runGenerate(_ *cobra.Command, _ []string) error {
if crdArgs.stdOut {
w = os.Stdout
} else {
outputLocation := filepath.Join(crdArgs.output, crd.Name+"_sample."+crdArgs.format)
outputLocation := filepath.Join(crdArgs.output, crd.Kind+"_sample."+crdArgs.format)
// closed later during render
outputFile, err := os.Create(outputLocation)
if err != nil {
Expand Down
14 changes: 10 additions & 4 deletions cmd/file_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import (
"fmt"
"os"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/Skarlso/crd-to-sample-yaml/pkg"
"github.com/Skarlso/crd-to-sample-yaml/pkg/sanitize"
)

type FileHandler struct {
location string
}

func (h *FileHandler) CRDs() ([]*v1beta1.CustomResourceDefinition, error) {
func (h *FileHandler) CRDs() ([]*pkg.SchemaType, error) {
if _, err := os.Stat(h.location); os.IsNotExist(err) {
return nil, fmt.Errorf("file under '%s' does not exist", h.location)
}
Expand All @@ -28,10 +29,15 @@ func (h *FileHandler) CRDs() ([]*v1beta1.CustomResourceDefinition, error) {
return nil, fmt.Errorf("failed to sanitize content: %w", err)
}

crd := &v1beta1.CustomResourceDefinition{}
crd := &unstructured.Unstructured{}
if err := yaml.Unmarshal(content, crd); err != nil {
return nil, fmt.Errorf("failed to unmarshal into custom resource definition: %w", err)
}

return []*v1beta1.CustomResourceDefinition{crd}, nil
schemaType, err := pkg.ExtractSchemaType(crd)
if err != nil {
return nil, fmt.Errorf("failed to extract schema type: %w", err)
}

return []*pkg.SchemaType{schemaType}, nil
}
15 changes: 10 additions & 5 deletions cmd/folder_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,23 @@ import (
"os"
"path/filepath"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/Skarlso/crd-to-sample-yaml/pkg"
"github.com/Skarlso/crd-to-sample-yaml/pkg/sanitize"
)

type FolderHandler struct {
location string
}

func (h *FolderHandler) CRDs() ([]*v1beta1.CustomResourceDefinition, error) {
func (h *FolderHandler) CRDs() ([]*pkg.SchemaType, error) {
if _, err := os.Stat(h.location); os.IsNotExist(err) {
return nil, fmt.Errorf("file under '%s' does not exist", h.location)
}

var crds []*v1beta1.CustomResourceDefinition
var crds []*pkg.SchemaType

if err := filepath.Walk(h.location, func(path string, info fs.FileInfo, err error) error {
if err != nil {
Expand All @@ -48,14 +49,18 @@ func (h *FolderHandler) CRDs() ([]*v1beta1.CustomResourceDefinition, error) {
return fmt.Errorf("failed to sanitize content: %w", err)
}

crd := &v1beta1.CustomResourceDefinition{}
crd := &unstructured.Unstructured{}
if err := yaml.Unmarshal(content, crd); err != nil {
fmt.Fprintln(os.Stderr, "skipping none CRD file: "+path)

return nil //nolint:nilerr // intentional
}
schemaType, err := pkg.ExtractSchemaType(crd)
if err != nil {
return fmt.Errorf("failed to extract schema type: %w", err)
}

crds = append(crds, crd)
crds = append(crds, schemaType)

return nil
}); err != nil {
Expand Down
14 changes: 7 additions & 7 deletions cmd/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,20 @@ func runGenerateSchema(_ *cobra.Command, _ []string) error {
}

for _, crd := range crds {
for _, v := range crd.Spec.Versions {
if v.Schema.OpenAPIV3Schema.ID == "" {
v.Schema.OpenAPIV3Schema.ID = "https://crdtoyaml.com/" + crd.Spec.Names.Kind + "." + crd.Spec.Group + "." + v.Name + ".schema.json"
for _, v := range crd.Versions {
if v.Schema.ID == "" {
v.Schema.ID = "https://crdtoyaml.com/" + crd.Kind + "." + crd.Group + "." + v.Name + ".schema.json"
}
if v.Schema.OpenAPIV3Schema.Schema == "" {
v.Schema.OpenAPIV3Schema.Schema = "https://json-schema.org/draft/2020-12/schema"
if v.Schema.Schema == "" {
v.Schema.Schema = "https://json-schema.org/draft/2020-12/schema"
}
content, err := json.Marshal(v.Schema.OpenAPIV3Schema)
content, err := json.Marshal(v.Schema)
if err != nil {
return fmt.Errorf("failed to marshal schema: %w", err)
}

const perm = 0o600
if err := os.WriteFile(filepath.Join(schemaArgs.outputFolder, crd.Spec.Names.Kind+"."+crd.Spec.Group+"."+v.Name+".schema.json"), content, perm); err != nil {
if err := os.WriteFile(filepath.Join(schemaArgs.outputFolder, crd.Kind+"."+crd.Group+"."+v.Name+".schema.json"), content, perm); err != nil {
return fmt.Errorf("failed to write schema: %w", err)
}
}
Expand Down
13 changes: 9 additions & 4 deletions cmd/url_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import (
"net/http"
"time"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/util/yaml"

"github.com/Skarlso/crd-to-sample-yaml/pkg"
"github.com/Skarlso/crd-to-sample-yaml/pkg/fetcher"
"github.com/Skarlso/crd-to-sample-yaml/pkg/sanitize"
)
Expand All @@ -21,7 +22,7 @@ type URLHandler struct {
token string
}

func (h *URLHandler) CRDs() ([]*v1beta1.CustomResourceDefinition, error) {
func (h *URLHandler) CRDs() ([]*pkg.SchemaType, error) {
client := http.DefaultClient
client.Timeout = timeout * time.Second

Expand All @@ -36,10 +37,14 @@ func (h *URLHandler) CRDs() ([]*v1beta1.CustomResourceDefinition, error) {
return nil, fmt.Errorf("failed to sanitize content: %w", err)
}

crd := &v1beta1.CustomResourceDefinition{}
crd := &unstructured.Unstructured{}
if err := yaml.Unmarshal(content, crd); err != nil {
return nil, fmt.Errorf("failed to unmarshal into custom resource definition: %w", err)
}
schemaType, err := pkg.ExtractSchemaType(crd)
if err != nil {
return nil, fmt.Errorf("failed to extract schema type: %w", err)
}

return []*v1beta1.CustomResourceDefinition{crd}, nil
return []*pkg.SchemaType{schemaType}, nil
}
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ go 1.23
require (
github.com/brianvoe/gofakeit/v6 v6.28.0
github.com/fatih/color v1.17.0
github.com/fxamacker/cbor/v2 v2.7.0
github.com/google/go-cmp v0.6.0
github.com/jedib0t/go-pretty/v6 v6.6.0
github.com/maxence-charriere/go-app/v10 v10.0.7
github.com/spf13/cobra v1.8.1
Expand All @@ -20,7 +22,6 @@ require (
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
Expand Down
33 changes: 33 additions & 0 deletions hack/keep_json_schema_uptodate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/usr/bin/env bash

set -e

# Move into the directory of repo_a and setup git user
git config user.name "GitHub Action"
git config user.email "[email protected]"

# Copy from api server to local
BRANCH_NAME="update-file-$(date +%Y%m%d%H%M%S)"
FILE_PATH=this/pkg/types_jsonschema.go
cp apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types_jsonschema.go this/pkg

# Check if there is a difference between the files
if git diff --exit-code "$FILE_PATH"; then
echo "No changes detected, exiting."
exit 0
fi

echo "Changes detected, creating a pull request..."

# Stage the changes
git add "$FILE_PATH"
git commit -m "Updated $FILE_PATH from repository B"

# Push the branch to repository A
git push origin "$BRANCH_NAME"

# Create a pull request using the GitHub CLI
gh auth login --with-token <<< "$GITHUB_TOKEN"
gh pr create --title "Sync $FILE_PATH from repo B" --body "This PR updates $FILE_PATH from repository B" --head "$BRANCH_NAME" --base main

echo "Pull request created successfully."
11 changes: 11 additions & 0 deletions pkg/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Files in this folder

Some of the files in this folder are taken from the following repository verbatim:
[Kubernetes Extensions api server repository](https://github.com/kubernetes/apiextensions-apiserver)

The following files are copied over:
- types_jsonschema.go
- marshal.go
- marshal_test.go

This is to ensure that we marshal the JSON schema types correctly.
32 changes: 14 additions & 18 deletions pkg/create_html_output.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import (
"io/fs"
"slices"
"sort"

"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
)

type Index struct {
Expand Down Expand Up @@ -66,7 +64,7 @@ func LoadTemplates() error {
}

// RenderContent creates an HTML website from the CRD content.
func RenderContent(w io.WriteCloser, crds []*v1beta1.CustomResourceDefinition, comments, minimal bool) (err error) {
func RenderContent(w io.WriteCloser, crds []*SchemaType, comments, minimal bool) (err error) {
defer func() {
if cerr := w.Close(); cerr != nil {
err = errors.Join(err, cerr)
Expand All @@ -77,10 +75,10 @@ func RenderContent(w io.WriteCloser, crds []*v1beta1.CustomResourceDefinition, c

for _, crd := range crds {
versions := make([]Version, 0)
parser := NewParser(crd.Spec.Group, crd.Spec.Names.Kind, comments, minimal, false)
parser := NewParser(crd.Group, crd.Kind, comments, minimal, false)

for _, version := range crd.Spec.Versions {
v, err := generate(crd, version.Schema.OpenAPIV3Schema, minimal, parser)
for _, version := range crd.Versions {
v, err := generate(version.Name, crd.Group, crd.Kind, version.Schema, minimal, parser)
if err != nil {
return fmt.Errorf("failed to generate yaml sample: %w", err)
}
Expand All @@ -89,10 +87,8 @@ func RenderContent(w io.WriteCloser, crds []*v1beta1.CustomResourceDefinition, c
}

// parse validation instead
if len(versions) == 0 && crd.Spec.Validation != nil {
crd.Spec.Validation.OpenAPIV3Schema.Properties["kind"] = v1beta1.JSONSchemaProps{}
crd.Spec.Validation.OpenAPIV3Schema.Properties["apiVersion"] = v1beta1.JSONSchemaProps{}
version, err := generate(crd, crd.Spec.Validation.OpenAPIV3Schema, minimal, parser)
if len(versions) == 0 && crd.Validation != nil {
version, err := generate(crd.Validation.Name, crd.Group, crd.Kind, crd.Validation.Schema, minimal, parser)
if err != nil {
return fmt.Errorf("failed to generate yaml sample: %w", err)
}
Expand All @@ -103,7 +99,7 @@ func RenderContent(w io.WriteCloser, crds []*v1beta1.CustomResourceDefinition, c
}

view := ViewPage{
Title: crd.Spec.Names.Kind,
Title: crd.Kind,
Versions: versions,
}

Expand All @@ -123,22 +119,22 @@ func RenderContent(w io.WriteCloser, crds []*v1beta1.CustomResourceDefinition, c
return nil
}

func generate(crd *v1beta1.CustomResourceDefinition, properties *v1beta1.JSONSchemaProps, minimal bool, parser *Parser) (Version, error) {
out, err := parseCRD(properties.Properties, crd.Name, minimal, RootRequiredFields)
func generate(name, group, kind string, properties *JSONSchemaProps, minimal bool, parser *Parser) (Version, error) {
out, err := parseCRD(properties.Properties, name, minimal, RootRequiredFields)
if err != nil {
return Version{}, fmt.Errorf("failed to parse properties: %w", err)
}
var buffer []byte
buf := bytes.NewBuffer(buffer)
if err := parser.ParseProperties(crd.Name, buf, properties.Properties); err != nil {
if err := parser.ParseProperties(name, buf, properties.Properties); err != nil {
return Version{}, fmt.Errorf("failed to generate yaml sample: %w", err)
}

return Version{
Version: crd.Name,
Version: name,
Properties: out,
Kind: crd.Spec.Names.Kind,
Group: crd.Spec.Group,
Kind: kind,
Group: group,
Description: properties.Description,
YAML: buf.String(),
}, nil
Expand All @@ -161,7 +157,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, minimal bool, requiredList []string) ([]*Property, error) {
func parseCRD(properties map[string]JSONSchemaProps, version string, minimal bool, requiredList []string) ([]*Property, error) {
output := make([]*Property, 0, len(properties))
sortedKeys := make([]string, 0, len(properties))

Expand Down
Loading

0 comments on commit c884d59

Please sign in to comment.