From d66c060fa4f73bc1808f8263268a61060ffb285f Mon Sep 17 00:00:00 2001 From: Peefy Date: Wed, 6 Dec 2023 15:26:54 +0800 Subject: [PATCH] feat: support yaml stream format input for CRD generation (#73) Signed-off-by: peefy --- pkg/kube_resource/generator/generator.go | 67 +++++++++++++++++-- pkg/kube_resource/generator/generator_test.go | 39 +++++++---- 2 files changed, 88 insertions(+), 18 deletions(-) diff --git a/pkg/kube_resource/generator/generator.go b/pkg/kube_resource/generator/generator.go index 52d21ce..e1789d6 100644 --- a/pkg/kube_resource/generator/generator.go +++ b/pkg/kube_resource/generator/generator.go @@ -15,10 +15,10 @@ package generator import ( + "bytes" "encoding/json" "errors" "fmt" - "io/ioutil" "os" "path/filepath" @@ -57,7 +57,7 @@ func GetSpec(opts *GenOpts) (string, error) { if err != nil { return "", fmt.Errorf("could not locate spec: %s, err: %s", opts.Spec, err) } - crdContent, err := ioutil.ReadFile(path) + crdContent, err := os.ReadFile(path) if err != nil { return "", fmt.Errorf("could not load spec: %s, err: %s", opts.Spec, err) } @@ -72,9 +72,12 @@ func GetSpec(opts *GenOpts) (string, error) { return "", fmt.Errorf("could not validate swagger spec: %s, err: %s", opts.Spec, err) } tmpSpecDir := os.TempDir() - tmpFile, err := ioutil.TempFile(tmpSpecDir, "kcl-swagger-") + tmpFile, err := os.CreateTemp(tmpSpecDir, "kcl-swagger-") + if err != nil { + return "", fmt.Errorf("could not validate swagger spec: %s, err: %s", opts.Spec, err) + } // copy k8s.json to tmpDir - if err := ioutil.WriteFile(filepath.Join(tmpSpecDir, "k8s.json"), []byte(k8sFile), 0644); err != nil { + if err := os.WriteFile(filepath.Join(tmpSpecDir, "k8s.json"), []byte(k8sFile), 0644); err != nil { return "", fmt.Errorf("could not generate swagger spec file: %s, err: %s", opts.Spec, err) } if _, err := tmpFile.Write(swaggerContent); err != nil { @@ -84,6 +87,62 @@ func GetSpec(opts *GenOpts) (string, error) { return tmpFile.Name(), nil } +// GetSpecs retrieves specifications from the given GenOpts and returns a list of temporary file paths for the generated OpenAPI specs. +// It returns an error if there is any issue in fetching and generating the specs. +// Parameters: +// - opts: a GenOpts struct that contains the options and parameters required for generating the specs +// Returns: +// - []string: a list of temporary file paths for the generated OpenAPI specs +// - error: an error message if any error occurs. +func GetSpecs(opts *GenOpts) ([]string, error) { + var result []string + // read crd content from file + path, err := filepath.Abs(opts.Spec) + if err != nil { + return result, fmt.Errorf("could not locate spec: %s, err: %s", opts.Spec, err) + } + crdContent, err := os.ReadFile(path) + if err != nil { + return result, fmt.Errorf("could not load spec: %s, err: %s", opts.Spec, err) + } + contents := separateSubDocuments(crdContent) + for _, content := range contents { + // generate openapi spec from crd + swagger, err := generate(string(content)) + if err != nil { + return result, fmt.Errorf("could not generate swagger spec: %s, err: %s", opts.Spec, err) + } + // write openapi spec to tmp file, along with the referenced k8s.json + swaggerContent, err := json.MarshalIndent(swagger, "", "") + if err != nil { + return result, fmt.Errorf("could not validate swagger spec: %s, err: %s", opts.Spec, err) + } + tmpSpecDir := os.TempDir() + tmpFile, err := os.CreateTemp(tmpSpecDir, "kcl-swagger-") + if err != nil { + return result, fmt.Errorf("could not validate swagger spec: %s, err: %s", opts.Spec, err) + } + // copy k8s.json to tmpDir + if err := os.WriteFile(filepath.Join(tmpSpecDir, "k8s.json"), []byte(k8sFile), 0644); err != nil { + return result, fmt.Errorf("could not generate swagger spec file: %s, err: %s", opts.Spec, err) + } + if _, err := tmpFile.Write(swaggerContent); err != nil { + return result, fmt.Errorf("could not generate swagger spec file: %s, err: %s", opts.Spec, err) + } + // Append the tmp openapi spec file path + result = append(result, tmpFile.Name()) + } + return result, nil +} + +func separateSubDocuments(data []byte) [][]byte { + lineBreak := "\n" + if bytes.Contains(data, []byte("\r\n---\r\n")) { + lineBreak = "\r\n" + } + return bytes.Split(data, []byte(lineBreak+"---"+lineBreak)) +} + // generate swagger model based on crd func generate(crdYaml string) (*spec.Swagger, error) { crdObj, _, err := scheme.Codecs.UniversalDeserializer(). diff --git a/pkg/kube_resource/generator/generator_test.go b/pkg/kube_resource/generator/generator_test.go index 387f9c9..663bbb4 100644 --- a/pkg/kube_resource/generator/generator_test.go +++ b/pkg/kube_resource/generator/generator_test.go @@ -8,15 +8,6 @@ import ( "k8s.io/client-go/kubernetes/scheme" ) -func TestGenerate(t *testing.T) { - swagger, err := generate(workload) - if err != nil { - t.Fatalf("error: %v", err) - } - data, err := json.MarshalIndent(swagger, "", " ") - fmt.Println(string(data)) -} - const ( workload = ` --- @@ -527,10 +518,7 @@ status: conditions: [] storedVersions: [] ` -) - -func TestCrdObj2CrdInternal(t *testing.T) { - v1Crd := ` + v1Crd = ` --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition @@ -564,7 +552,7 @@ spec: shortNames: - ct ` - v1beta1Crd := ` + v1beta1Crd = ` --- # Deprecated in v1.16 in favor of apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1 @@ -609,6 +597,9 @@ spec: - ct preserveUnknownFields: false ` +) + +func TestCrdObj2CrdInternal(t *testing.T) { crds := []string{v1Crd, v1beta1Crd} for _, crdYaml := range crds { crdObj, _, _ := scheme.Codecs.UniversalDeserializer(). @@ -619,3 +610,23 @@ spec: } } } + +func TestGenerate(t *testing.T) { + swagger, err := generate(workload) + if err != nil { + t.Fatalf("error: %v", err) + } + data, err := json.MarshalIndent(swagger, "", " ") + if err != nil { + t.Errorf("generate failed. err: %s", err) + } + fmt.Println(string(data)) +} + +func TestSeparateSubDocuments(t *testing.T) { + crds := v1Crd + v1beta1Crd + files := separateSubDocuments([]byte(crds)) + if len(files) != 3 { + t.Errorf("separateSubDocuments failed. expected 3, got %d", len(files)) + } +}