Skip to content

Commit 14ba2d8

Browse files
author
Mohd Uzair
authored
Merge pull request #582 from MUzairS15/MUzairS15/generator/openapi
support generation of model from openapi schemas
2 parents a8d2a6d + 98a1e99 commit 14ba2d8

File tree

10 files changed

+285
-45
lines changed

10 files changed

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

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)