kruise-rollout-api/cmd/gen-schema/main.go

193 lines
5.5 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"os/exec"
"strings"
semver "github.com/blang/semver/v4"
"k8s.io/kube-openapi/pkg/common"
kubeopenapiutil "k8s.io/kube-openapi/pkg/util"
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/openkruise/kruise-rollout-api/pkg/rollouts"
)
var K8SVersions = []int{18, 21, 24}
type xKubernetesGroupVersionKind struct {
Group string `json:"group"`
Kind string `json:"kind"`
Version string `json:"version"`
}
type gvkMeta struct {
XKubernetesGroupVersionKind []xKubernetesGroupVersionKind `json:"x-kubernetes-group-version-kind"`
}
type k8sGvkMapping struct {
Definitions map[string]gvkMeta `json:"definitions"`
}
type openAPISchema struct {
spec.Schema
XKubernetesGroupVersionKind []xKubernetesGroupVersionKind `json:"x-kubernetes-group-version-kind"`
}
func (s openAPISchema) MarshalJSON() ([]byte, error) {
b1, err := json.Marshal(s.Schema)
if err != nil {
return nil, fmt.Errorf("schema %v", err)
}
if s.XKubernetesGroupVersionKind != nil {
b2, err := json.Marshal(s.XKubernetesGroupVersionKind)
if err != nil {
return nil, fmt.Errorf("x-kubernetes-group-version-kind %v", err)
}
b1 = append(b1[:len(b1)-1], fmt.Sprintf(",\"x-kubernetes-group-version-kind\":%s}", string(b2))...)
}
return b1, nil
}
func checkErr(err error) {
if err != nil {
panic(err)
}
}
func generateOpenApiSchema(outputPath string, GetOpenAPIDefinitions func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition) error {
// We replace the generated names with group specific names aka kruise is `apps.kruise.io` instead of the real
// group kind because within all the openkruise projects we have overlapping types due to all openkruise projects being under the same
// kruise.io group. Kustomize does not care about the name as long as all the links match up and the `x-kubernetes-group-version-kind`
// metadata is correct
var kruiseMappings = map[string]string{
"github.com/openkruise/kruise-rollout-api/rollouts": "rollouts.kruise.io",
}
defMap := GetOpenAPIDefinitions(func(path string) spec.Ref {
for k, v := range kruiseMappings {
path = strings.ReplaceAll(path, k, v)
}
return spec.MustCreateRef(fmt.Sprintf("#/definitions/%s", kubeopenapiutil.ToRESTFriendlyName(path)))
})
var crdDefs = make(map[string]openAPISchema)
for pathKey, definition := range defMap {
for k, v := range kruiseMappings {
pathKey = strings.ReplaceAll(pathKey, k, v)
}
crdDefs[kubeopenapiutil.ToRESTFriendlyName(pathKey)] = openAPISchema{
Schema: definition.Schema,
XKubernetesGroupVersionKind: make([]xKubernetesGroupVersionKind, 0),
}
}
k8sDefs, err := loadK8SDefinitions()
checkErr(err)
for k, v := range crdDefs {
//We pull out openkruise crd information based on the dot pattern of the key in the dictionary we are also setting it for all
//argo types instead of just the ones needed this could be incorrect as far as spec goes, but it works.
if strings.HasPrefix(k, "io.kruise") {
kruiseGVK := strings.Split(k, ".")
v.XKubernetesGroupVersionKind = []xKubernetesGroupVersionKind{
{
Group: fmt.Sprintf("%s.kruise.io", kruiseGVK[2]),
Kind: kruiseGVK[4],
Version: kruiseGVK[3],
},
}
crdDefs[k] = v
continue
}
// Pull the group version kind information from the k8s definitions that downloaded via loadK8SDefinitions
if entry, ok := k8sDefs.Definitions[k]; ok {
if len(entry.XKubernetesGroupVersionKind) > 0 {
v.XKubernetesGroupVersionKind = entry.XKubernetesGroupVersionKind
crdDefs[k] = v
}
}
}
data, err := json.MarshalIndent(map[string]interface{}{
"definitions": crdDefs,
}, "", " ")
if err != nil {
return err
}
return os.WriteFile(outputPath, data, 0644)
}
// loadK8SDefinitions loads K8S types API schema definitions starting with the version specified in go.mod then the fucnction
// parameter versions
func loadK8SDefinitions() (*k8sGvkMapping, error) {
// detects minor version of k8s client
k8sVersionCmd := exec.Command("sh", "-c", "cat go.mod | grep \"k8s.io/client-go\" | head -n 1 | cut -d' ' -f2")
versionData, err := k8sVersionCmd.Output()
if err != nil {
return nil, fmt.Errorf("failed to determine k8s client version: %v", err)
}
v, err := semver.Parse(strings.TrimSpace(strings.Replace(string(versionData), "v", "", 1)))
if err != nil {
return nil, err
}
schemaGoMod := k8sGvkMapping{}
err = downloadK8SDefinitions(uint(v.Minor), &schemaGoMod)
if err != nil {
return nil, err
}
for _, v := range K8SVersions {
// Download fixe old version to keep old schema's compatibility
schemaFixedVer := k8sGvkMapping{}
err = downloadK8SDefinitions(uint(v), &schemaFixedVer)
if err != nil {
return nil, err
}
// Merge old and new schema
for k, v := range schemaFixedVer.Definitions {
schemaGoMod.Definitions[k] = v
}
}
return &schemaGoMod, nil
}
func downloadK8SDefinitions(version uint, k8sGvkMapping *k8sGvkMapping) error {
resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/kubernetes/kubernetes/release-1.%d/api/openapi-spec/swagger.json", version))
if err != nil {
return err
}
defer func() {
_ = resp.Body.Close()
}()
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(data, k8sGvkMapping)
if err != nil {
return err
}
return nil
}
// Generate CRD spec for Resources in OpenKruise
func main() {
err := generateOpenApiSchema("schema/openkruise_rollouts_kustomize_schema.json", rollouts.GetOpenAPIDefinitions)
checkErr(err)
}