Skip to content

Commit 3addd46

Browse files
author
Mohd Uzair
authored
Merge pull request #603 from meshery/revert-600-revert-582-MUzairS15/generator/openapi
Revert "Revert "support generation of model from openapi schemas""
2 parents 38beeb7 + 791faef commit 3addd46

File tree

11 files changed

+282
-45
lines changed

11 files changed

+282
-45
lines changed

encoding/convert.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package encoding
2+
3+
import (
4+
"gopkg.in/yaml.v3"
5+
)
6+
7+
func ToYaml(data []byte) ([]byte, error) {
8+
var out map[string]interface{}
9+
err := Unmarshal(data, &out)
10+
if err != nil {
11+
return nil, err
12+
}
13+
14+
return yaml.Marshal(out)
15+
}

generators/artifacthub/package.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,14 @@ func (pkg AhPackage) GetVersion() string {
3838
return pkg.Version
3939
}
4040

41+
func (pkg AhPackage) GetSourceURL() string {
42+
return pkg.ChartUrl
43+
}
44+
45+
func (pkg AhPackage) GetName() string {
46+
return pkg.Name
47+
}
48+
4149
func (pkg AhPackage) GenerateComponents() ([]_component.ComponentDefinition, error) {
4250
components := make([]_component.ComponentDefinition, 0)
4351
// TODO: Move this to the configuration

generators/github/package.go

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/layer5io/meshkit/utils"
88
"github.com/layer5io/meshkit/utils/component"
9+
"github.com/layer5io/meshkit/utils/kubernetes"
910
"github.com/layer5io/meshkit/utils/manifests"
1011
"github.com/meshery/schemas/models/v1beta1/category"
1112
_component "github.com/meshery/schemas/models/v1beta1/component"
@@ -25,6 +26,14 @@ func (gp GitHubPackage) GetVersion() string {
2526
return gp.version
2627
}
2728

29+
func (gp GitHubPackage) GetSourceURL() string {
30+
return gp.SourceURL
31+
}
32+
33+
func (gp GitHubPackage) GetName() string {
34+
return gp.Name
35+
}
36+
2837
func (gp GitHubPackage) GenerateComponents() ([]_component.ComponentDefinition, error) {
2938
components := make([]_component.ComponentDefinition, 0)
3039

@@ -34,28 +43,40 @@ func (gp GitHubPackage) GenerateComponents() ([]_component.ComponentDefinition,
3443
}
3544

3645
manifestBytes := bytes.Split(data, []byte("\n---\n"))
37-
crds, errs := component.FilterCRDs(manifestBytes)
46+
errs := []error{}
3847

39-
for _, crd := range crds {
40-
comp, err := component.Generate(crd)
41-
if err != nil {
42-
continue
43-
}
44-
if comp.Model.Metadata == nil {
45-
comp.Model.Metadata = &model.ModelDefinition_Metadata{}
46-
}
47-
if comp.Model.Metadata.AdditionalProperties == nil {
48-
comp.Model.Metadata.AdditionalProperties = make(map[string]interface{})
49-
}
48+
for _, crd := range manifestBytes {
49+
isCrd := kubernetes.IsCRD(string(crd))
50+
if !isCrd {
5051

51-
comp.Model.Metadata.AdditionalProperties["source_uri"] = gp.SourceURL
52-
comp.Model.Version = gp.version
53-
comp.Model.Name = gp.Name
54-
comp.Model.Category = category.CategoryDefinition{
55-
Name: "",
52+
comps, err := component.GenerateFromOpenAPI(string(crd), gp)
53+
if err != nil {
54+
errs = append(errs, component.ErrGetSchema(err))
55+
continue
56+
}
57+
components = append(components, comps...)
58+
} else {
59+
comp, err := component.Generate(string(crd))
60+
if err != nil {
61+
continue
62+
}
63+
if comp.Model.Metadata == nil {
64+
comp.Model.Metadata = &model.ModelDefinition_Metadata{}
65+
}
66+
if comp.Model.Metadata.AdditionalProperties == nil {
67+
comp.Model.Metadata.AdditionalProperties = make(map[string]interface{})
68+
}
69+
70+
comp.Model.Metadata.AdditionalProperties["source_uri"] = gp.SourceURL
71+
comp.Model.Version = gp.version
72+
comp.Model.Name = gp.Name
73+
comp.Model.Category = category.CategoryDefinition{
74+
Name: "",
75+
}
76+
comp.Model.DisplayName = manifests.FormatToReadableString(comp.Model.Name)
77+
components = append(components, comp)
5678
}
57-
comp.Model.DisplayName = manifests.FormatToReadableString(comp.Model.Name)
58-
components = append(components, comp)
79+
5980
}
6081

6182
return components, utils.CombineErrors(errs, "\n")

generators/models/interfaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ type Validator interface {
1313
type Package interface {
1414
GenerateComponents() ([]component.ComponentDefinition, error)
1515
GetVersion() string
16+
GetSourceURL() string
17+
GetName() string
1618
}
1719

1820
// Supports pulling packages from Artifact Hub and other sources like Docker Hub.

utils/component/error.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ const (
99
ErrUpdateSchemaCode = "meshkit-11158"
1010
)
1111

12+
var ErrNoSchemasFound = errors.New(ErrGetSchemaCode, errors.Alert, []string{"Could not get schema for the given openapi spec"}, []string{"The OpenAPI spec doesn't include \"components.schemas\" path"}, []string{"The spec doesn't have include any schema"}, []string{"Verify the spec has valid schema."})
13+
1214
// No reference usage found. Also check in adapters before deleting
1315
func ErrCrdGenerate(err error) error {
1416
return errors.New(ErrCrdGenerateCode, errors.Alert, []string{"Could not generate component with the given CRD"}, []string{err.Error()}, []string{""}, []string{"Verify CRD has valid schema."})

utils/component/generator.go

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,28 @@ var DefaultPathConfig2 = CuePathConfig{
4545
SpecPath: "spec.validation.openAPIV3Schema",
4646
}
4747

48+
var OpenAPISpecPathConfig = CuePathConfig{
49+
NamePath: `x-kubernetes-group-version-kind"[0].kind`,
50+
IdentifierPath: "spec.names.kind",
51+
VersionPath: `"x-kubernetes-group-version-kind"[0].version`,
52+
GroupPath: `"x-kubernetes-group-version-kind"[0].group`,
53+
ScopePath: "spec.scope",
54+
SpecPath: "spec.versions[0].schema.openAPIV3Schema",
55+
PropertiesPath: "properties",
56+
}
57+
4858
var Configs = []CuePathConfig{DefaultPathConfig, DefaultPathConfig2}
4959

50-
func Generate(crd string) (component.ComponentDefinition, error) {
60+
func Generate(resource string) (component.ComponentDefinition, error) {
5161
cmp := component.ComponentDefinition{}
5262
cmp.SchemaVersion = v1beta1.ComponentSchemaVersion
5363

5464
cmp.Metadata = component.ComponentDefinition_Metadata{}
55-
crdCue, err := utils.YamlToCue(crd)
65+
crdCue, err := utils.YamlToCue(resource)
5666
if err != nil {
5767
return cmp, err
5868
}
69+
5970
var schema string
6071
for _, cfg := range Configs {
6172
schema, err = getSchema(crdCue, cfg)

utils/component/openapi_generator.go

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
package component
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
"fmt"
7+
"strings"
8+
9+
"cuelang.org/go/cue"
10+
"cuelang.org/go/cue/cuecontext"
11+
cueJson "cuelang.org/go/encoding/json"
12+
"github.com/layer5io/meshkit/generators/models"
13+
"github.com/layer5io/meshkit/utils"
14+
"github.com/layer5io/meshkit/utils/manifests"
15+
16+
"gopkg.in/yaml.v3"
17+
18+
"github.com/meshery/schemas/models/v1beta1"
19+
"github.com/meshery/schemas/models/v1beta1/component"
20+
"github.com/meshery/schemas/models/v1beta1/model"
21+
)
22+
23+
func GenerateFromOpenAPI(resource string, pkg models.Package) ([]component.ComponentDefinition, error) {
24+
if resource == "" {
25+
return nil, nil
26+
}
27+
resource, err := getResolvedManifest(resource)
28+
if err != nil && errors.Is(err, ErrNoSchemasFound) {
29+
return nil, nil
30+
}
31+
if err != nil {
32+
return nil, err
33+
}
34+
cuectx := cuecontext.New()
35+
cueParsedManExpr, err := cueJson.Extract("", []byte(resource))
36+
if err != nil {
37+
return nil, err
38+
}
39+
40+
parsedManifest := cuectx.BuildExpr(cueParsedManExpr)
41+
definitions, err := utils.Lookup(parsedManifest, "components.schemas")
42+
43+
if err != nil {
44+
return nil, err
45+
}
46+
47+
fields, err := definitions.Fields()
48+
if err != nil {
49+
fmt.Printf("%v\n", err)
50+
return nil, err
51+
}
52+
components := make([]component.ComponentDefinition, 0)
53+
54+
for fields.Next() {
55+
fieldVal := fields.Value()
56+
kindCue, err := utils.Lookup(fieldVal, `"x-kubernetes-group-version-kind"[0].kind`)
57+
if err != nil {
58+
continue
59+
}
60+
kind, err := kindCue.String()
61+
kind = strings.ToLower(kind)
62+
if err != nil {
63+
fmt.Printf("%v", err)
64+
continue
65+
}
66+
67+
crd, err := fieldVal.MarshalJSON()
68+
if err != nil {
69+
fmt.Printf("%v", err)
70+
continue
71+
}
72+
versionCue, err := utils.Lookup(fieldVal, `"x-kubernetes-group-version-kind"[0].version`)
73+
if err != nil {
74+
continue
75+
}
76+
77+
groupCue, err := utils.Lookup(fieldVal, `"x-kubernetes-group-version-kind"[0].group`)
78+
if err != nil {
79+
continue
80+
}
81+
82+
apiVersion, _ := versionCue.String()
83+
if g, _ := groupCue.String(); g != "" {
84+
apiVersion = g + "/" + apiVersion
85+
}
86+
modified := make(map[string]interface{}) //Remove the given fields which is either not required by End user (like status) or is prefilled by system (like apiVersion, kind and metadata)
87+
err = json.Unmarshal(crd, &modified)
88+
if err != nil {
89+
fmt.Printf("%v", err)
90+
continue
91+
}
92+
93+
modifiedProps, err := UpdateProperties(fieldVal, cue.ParsePath("properties.spec"), apiVersion)
94+
if err == nil {
95+
modified = modifiedProps
96+
}
97+
98+
DeleteFields(modified)
99+
crd, err = json.Marshal(modified)
100+
if err != nil {
101+
fmt.Printf("%v", err)
102+
continue
103+
}
104+
105+
c := component.ComponentDefinition{
106+
SchemaVersion: v1beta1.ComponentSchemaVersion,
107+
Format: component.JSON,
108+
Component: component.Component{
109+
Kind: kind,
110+
Version: apiVersion,
111+
Schema: string(crd),
112+
},
113+
DisplayName: manifests.FormatToReadableString(kind),
114+
Model: model.ModelDefinition{
115+
SchemaVersion: v1beta1.ModelSchemaVersion,
116+
Model: model.Model{
117+
Version: pkg.GetVersion(),
118+
},
119+
Name: pkg.GetName(),
120+
DisplayName: manifests.FormatToReadableString(pkg.GetName()),
121+
Metadata: &model.ModelDefinition_Metadata{
122+
AdditionalProperties: map[string]interface{}{
123+
"source_uri": pkg.GetSourceURL(),
124+
},
125+
},
126+
},
127+
}
128+
129+
components = append(components, c)
130+
}
131+
return components, nil
132+
133+
}
134+
135+
func getResolvedManifest(manifest string) (string, error) {
136+
var m map[string]interface{}
137+
138+
err := yaml.Unmarshal([]byte(manifest), &m)
139+
if err != nil {
140+
return "", utils.ErrDecodeYaml(err)
141+
}
142+
143+
byt, err := json.Marshal(m)
144+
if err != nil {
145+
return "", utils.ErrMarshal(err)
146+
}
147+
148+
cuectx := cuecontext.New()
149+
cueParsedManExpr, err := cueJson.Extract("", byt)
150+
if err != nil {
151+
return "", ErrGetSchema(err)
152+
}
153+
154+
parsedManifest := cuectx.BuildExpr(cueParsedManExpr)
155+
definitions, err := utils.Lookup(parsedManifest, "components.schemas")
156+
if err != nil {
157+
return "", ErrNoSchemasFound
158+
}
159+
resol := manifests.ResolveOpenApiRefs{}
160+
cache := make(map[string][]byte)
161+
resolved, err := resol.ResolveReferences(byt, definitions, cache)
162+
if err != nil {
163+
return "", err
164+
}
165+
manifest = string(resolved)
166+
return manifest, nil
167+
}

utils/component/utils.go

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"github.com/layer5io/meshkit/utils"
88
"github.com/layer5io/meshkit/utils/kubernetes"
99
"github.com/layer5io/meshkit/utils/manifests"
10-
"gopkg.in/yaml.v2"
1110
)
1211

1312
// Remove the fields which is either not required by end user (like status) or is prefilled by system (like apiVersion, kind and metadata)
@@ -81,19 +80,10 @@ func FilterCRDs(manifests [][]byte) ([]string, []error) {
8180
var errs []error
8281
var filteredManifests []string
8382
for _, m := range manifests {
84-
85-
var crd map[string]interface{}
86-
err := yaml.Unmarshal(m, &crd)
87-
if err != nil {
88-
errs = append(errs, err)
89-
continue
90-
}
91-
92-
isCrd := kubernetes.IsCRD(crd)
93-
if !isCrd {
94-
continue
83+
isCrd := kubernetes.IsCRD(string(m))
84+
if isCrd {
85+
filteredManifests = append(filteredManifests, string(m))
9586
}
96-
filteredManifests = append(filteredManifests, string(m))
9787
}
9888
return filteredManifests, errs
9989
}

utils/helm/helm.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"regexp"
1010
"strings"
1111

12+
"github.com/layer5io/meshkit/encoding"
1213
"github.com/layer5io/meshkit/utils"
1314
"helm.sh/helm/v3/pkg/action"
1415
"helm.sh/helm/v3/pkg/chart"
@@ -108,7 +109,13 @@ func writeToFile(w io.Writer, path string) error {
108109
if err != nil {
109110
return utils.ErrReadFile(err, path)
110111
}
111-
_, err = w.Write(data)
112+
113+
byt, err := encoding.ToYaml(data)
114+
if err != nil {
115+
return utils.ErrWriteFile(err, path)
116+
}
117+
118+
_, err = w.Write(byt)
112119
if err != nil {
113120
return utils.ErrWriteFile(err, path)
114121
}

0 commit comments

Comments
 (0)