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 (#121)

* feat: add generating samples for any kind of object that supports openAPIV3schema

* fix defer closing write closer in function instead of at the place of creation

* adding release docs and README update
  • Loading branch information
Skarlso authored Oct 14, 2024
1 parent 1927b6b commit a792c40
Show file tree
Hide file tree
Showing 29 changed files with 2,057 additions and 181 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
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ please follow the [How to test CRDs with CTY Readme](./crd-testing-README.md).
![crd-unittest-sample-output](./imgs/crd-unittest-outcome.png)

## Getting started

- Prerequisites: Go installed on your machine. (Check out this link for details: https://go.dev/doc/install)
- Clone the repository
- Execute `make build` to build the binary
Expand Down Expand Up @@ -122,6 +123,33 @@ cty generate schema -r sample-crd
to target a folder.
## CRD Types
ANY kind of type can be used, not just `CustomResourceDefinitions` as long as they provide the following structure:
```yaml
# top level spec field
spec:
names:
kind: # this should be the kind of the generated object
group: # the group of the generated object
# optional version field
versions:
- name: v1alpha1
# OpenAPI schema (like the one used by Kubernetes CRDs). Determines what fields
# the XR (and claim) will have. Will be automatically extended by crossplane.
# See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/
# for full CRD documentation and guide on how to write OpenAPI schemas
schema:
openAPIV3Schema:
# optional validation field describing all versions
validation:
openAPIV3Schema:
```

If these fields are respected, the apiVersion or the kind of the resource doesn't matter. It's all unstructured in the
background.

## WASM frontend

There is a WASM based frontend that can be started by navigating into the `wasm` folder and running the following make
Expand Down
16 changes: 7 additions & 9 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 @@ -79,6 +78,11 @@ func runGenerate(_ *cobra.Command, _ []string) error {
}

var w io.WriteCloser
defer func() {
if err := w.Close(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to close output file: %s", err.Error())
}
}()

if crdArgs.format == FormatHTML {
if crdArgs.stdOut {
Expand All @@ -88,12 +92,6 @@ func runGenerate(_ *cobra.Command, _ []string) error {
if err != nil {
return fmt.Errorf("failed to create output file: %w", err)
}

defer func() {
if err := w.Close(); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to close output file: %s", err.Error())
}
}()
}

return pkg.RenderContent(w, crds, crdArgs.comments, crdArgs.minimal)
Expand All @@ -104,7 +102,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
}
21 changes: 21 additions & 0 deletions docs/release_notes/v1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Release v1.0.0

:boom: _**BREAKING CHANGE**_ :boom:

This is the first official 1.0.0 version of CRD sample. This tool has come a long way
from simply generating sample YAML files from CRDs. It now supports the following
major features:

- CRD testing
- Generating JSON schemas
- Validating more types other than CRD that support openAPIV3schema section in their `spec` field
- Generating minimum required yamls
- Adding comments
- Generating YAML that 100% conforms to the given schema, respecting minimum values, regex, formats, etc.
- A feature rich website with
- live code changes rendering
- back navigation
- lots of tiny options

This has been quite the journey. Hopefully, from now on, the API will be somewhat stable. I can't promise completey
unbreakable changes, but I promise not to break anything in Patch versions.
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.
Loading

0 comments on commit a792c40

Please sign in to comment.