193 lines
5.5 KiB
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)
|
|
}
|