Skip to content

Commit a807555

Browse files
committed
implement custom routes client at GV level
1 parent 36ba1c2 commit a807555

18 files changed

Lines changed: 531 additions & 33 deletions

File tree

benchmark/benchmark_mocks_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,3 +294,7 @@ func (m *mockClientGeneratorWithK8sClient) ClientFor(kind resource.Kind) (resour
294294
k8s.DefaultClientConfig(),
295295
)
296296
}
297+
298+
func (m *mockClientGeneratorWithK8sClient) ClientForGV(gv schema.GroupVersion) (resource.GroupVersionClient, error) {
299+
return nil, nil
300+
}

codegen/cuekind/generators.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ func ManifestGoGenerator(pkg string, includeSchemas bool, projectRepo, goGenPath
180180
},
181181
&jennies.ResourceClientJenny{
182182
GroupByKind: !groupKinds,
183-
})
183+
}, &jennies.GroupVersionClientJenny{})
184184
return g
185185
}
186186

codegen/cuekind/generators_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ func TestResourceGenerator(t *testing.T) {
6060
files, err := ResourceGenerator("codegen-tests", "pkg/generated", false).Generate(kinds...)
6161
require.NoError(t, err)
6262
// Check number of files generated
63-
// 12 (7 -> object, spec, status, schema, codec, constants) * 2 versions
64-
assert.Len(t, files, 12, "should be 12 files generated, got %d", len(files))
63+
// 14 = 12 resource files across 2 versions plus 2 app package constants files for the gv client packages
64+
assert.Len(t, files, 14, "should be 14 files generated, got %d", len(files))
6565
// Check content against the golden files
6666
compareToGolden(t, files, "go/groupbykind")
6767
})

codegen/jennies/constants.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ func (c *Constants) Generate(appManifest codegen.AppManifest) (codejen.Files, er
3939
path: filepath.Join(path, "constants.go"),
4040
}
4141
}
42+
if !c.GroupByKind {
43+
continue
44+
}
45+
appPath := GetGeneratedGoTypePath(false, appManifest.Properties().Group, v.Name(), k.MachineName)
46+
if _, ok := m[appPath]; ok {
47+
continue
48+
}
49+
m[appPath] = constantsFileParams{
50+
group: appManifest.Properties().FullGroup,
51+
version: v.Name(),
52+
path: filepath.Join(appPath, "constants.go"),
53+
}
4254
}
4355
files := make(codejen.Files, 0)
4456
for _, v := range m {

codegen/jennies/goclients.go

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -61,14 +61,14 @@ func (r *ResourceClientJenny) Generate(appManifest codegen.AppManifest) (codejen
6161
KindName: exportField(kind.Kind),
6262
KindPrefix: prefix,
6363
Subresources: subresources,
64-
CustomRoutes: make([]templates.GoResourceClientCustomRoute, 0),
64+
CustomRoutes: make([]templates.GoClientCustomRoute, 0),
6565
}
6666
for cpath, methods := range kind.Routes {
6767
for method, route := range methods {
6868
if route.Name == "" {
6969
route.Name = defaultRouteName(method, cpath)
7070
}
71-
crmd, err := r.getCustomRouteInfo(route)
71+
crmd, err := getCustomRouteInfo(route)
7272
if err != nil {
7373
return nil, err
7474
}
@@ -77,7 +77,7 @@ func (r *ResourceClientJenny) Generate(appManifest codegen.AppManifest) (codejen
7777
md.CustomRoutes = append(md.CustomRoutes, crmd)
7878
}
7979
}
80-
slices.SortFunc(md.CustomRoutes, func(a, b templates.GoResourceClientCustomRoute) int {
80+
slices.SortFunc(md.CustomRoutes, func(a, b templates.GoClientCustomRoute) int {
8181
return strings.Compare(a.TypeName, b.TypeName)
8282
})
8383

@@ -105,24 +105,98 @@ func (r *ResourceClientJenny) Generate(appManifest codegen.AppManifest) (codejen
105105
return files, nil
106106
}
107107

108-
func (*ResourceClientJenny) getCustomRouteInfo(customRoute codegen.CustomRoute) (templates.GoResourceClientCustomRoute, error) {
109-
md := templates.GoResourceClientCustomRoute{
108+
func getCustomRouteInfo(customRoute codegen.CustomRoute) (templates.GoClientCustomRoute, error) {
109+
md := templates.GoClientCustomRoute{
110110
TypeName: toExportedFieldName(customRoute.Name),
111111
HasParams: customRoute.Request.Query.Exists(),
112112
HasBody: customRoute.Request.Body.Exists(),
113113
}
114114
if md.HasParams {
115-
md.ParamValues = make([]templates.GoResourceClientParamValues, 0)
115+
md.ParamValues = make([]templates.GoCustomRouteParamValues, 0)
116116
it, err := customRoute.Request.Query.Fields()
117117
if err != nil {
118118
return md, err
119119
}
120120
for it.Next() {
121-
md.ParamValues = append(md.ParamValues, templates.GoResourceClientParamValues{
121+
md.ParamValues = append(md.ParamValues, templates.GoCustomRouteParamValues{
122122
Key: it.Selector().String(),
123123
FieldName: exportField(it.Selector().String()),
124124
})
125125
}
126126
}
127127
return md, nil
128128
}
129+
130+
type GroupVersionClientJenny struct{}
131+
132+
func (*GroupVersionClientJenny) JennyName() string {
133+
return "GroupVersionClientJenny"
134+
}
135+
136+
func (r *GroupVersionClientJenny) Generate(appManifest codegen.AppManifest) (codejen.Files, error) {
137+
files := make(codejen.Files, 0)
138+
for _, version := range appManifest.Versions() {
139+
md := templates.GoGroupVersionClientMetadata{
140+
PackageName: ToPackageName(version.Name()),
141+
ClientName: "GroupVersionClient",
142+
NamespacedRoutes: make([]templates.GoClientCustomRoute, 0),
143+
ClusterRoutes: make([]templates.GoClientCustomRoute, 0),
144+
}
145+
146+
for cpath, methods := range version.Routes().Namespaced {
147+
for method, route := range methods {
148+
if route.Name == "" {
149+
route.Name = defaultRouteName(method, cpath)
150+
}
151+
crmd, err := getCustomRouteInfo(route)
152+
if err != nil {
153+
return nil, err
154+
}
155+
crmd.Path = cpath
156+
crmd.Method = method
157+
md.NamespacedRoutes = append(md.NamespacedRoutes, crmd)
158+
}
159+
}
160+
161+
for cpath, methods := range version.Routes().Cluster {
162+
for method, route := range methods {
163+
if route.Name == "" {
164+
route.Name = defaultRouteName(method, cpath)
165+
}
166+
crmd, err := getCustomRouteInfo(route)
167+
if err != nil {
168+
return nil, err
169+
}
170+
crmd.Path = cpath
171+
crmd.Method = method
172+
md.ClusterRoutes = append(md.ClusterRoutes, crmd)
173+
}
174+
}
175+
176+
if len(md.NamespacedRoutes) == 0 && len(md.ClusterRoutes) == 0 {
177+
continue
178+
}
179+
180+
b := bytes.Buffer{}
181+
err := templates.WriteGroupVersionClient(md, &b)
182+
if err != nil {
183+
return nil, err
184+
}
185+
formatted, err := format.Source(b.Bytes())
186+
if err != nil {
187+
return nil, err
188+
}
189+
formatted, err = imports.Process("", formatted, &imports.Options{
190+
Comments: true,
191+
})
192+
if err != nil {
193+
return nil, err
194+
}
195+
files = append(files, codejen.File{
196+
RelativePath: filepath.Join(ToPackageName(appManifest.Properties().Group), ToPackageName(version.Name()), "client_gen.go"),
197+
Data: formatted,
198+
From: []codejen.NamedJenny{r},
199+
})
200+
}
201+
return files, nil
202+
}

codegen/templates/client.tmpl

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package {{.PackageName}}
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"net/url"
11+
12+
"github.com/grafana/grafana-app-sdk/resource"
13+
)
14+
15+
type {{.ClientName}} struct {
16+
client resource.GroupVersionClient
17+
}
18+
19+
func New{{.ClientName}}(client resource.GroupVersionClient) *{{.ClientName}} {
20+
return &{{.ClientName}}{
21+
client: client,
22+
}
23+
}
24+
25+
func New{{.ClientName}}FromGenerator(generator resource.ClientGenerator) (*{{.ClientName}}, error) {
26+
client, err := generator.ClientForGV(GroupVersion)
27+
if err != nil {
28+
return nil, err
29+
}
30+
return New{{.ClientName}}(client), nil
31+
}
32+
{{ range .NamespacedRoutes }}
33+
type {{.TypeName}}Request struct { {{ if .HasParams }}
34+
Params {{.TypeName}}RequestParams{{ end }}{{ if .HasBody }}
35+
Body {{.TypeName}}RequestBody{{ end }}
36+
Headers http.Header
37+
}
38+
39+
func (c *{{$.ClientName}}) {{.TypeName}}(ctx context.Context, namespace string, request {{.TypeName}}Request) (*{{.TypeName}}Response, error) { {{ if .HasParams }}
40+
params := url.Values{}{{ range .ParamValues }}
41+
params.Set("{{.Key}}", fmt.Sprintf("%v", request.Params.{{.FieldName}})){{ end }}{{ end }}{{ if .HasBody }}
42+
body, err := json.Marshal(request.Body)
43+
if err != nil {
44+
return nil, fmt.Errorf("unable to marshal body to JSON: %w", err)
45+
}{{ end }}
46+
resp, err := c.client.CustomRouteRequest(ctx, namespace, "", "", resource.CustomRouteRequestOptions{
47+
Path: "{{.Path}}",
48+
Verb: "{{.Method}}",{{ if .HasParams }}
49+
Query: params,{{ end }}{{ if .HasBody }}
50+
Body: io.NopCloser(bytes.NewReader(body)),{{ end }}
51+
Headers: request.Headers,
52+
})
53+
if err != nil {
54+
return nil, err
55+
}
56+
cast := {{.TypeName}}Response{}
57+
err = json.Unmarshal(resp, &cast)
58+
if err != nil {
59+
return nil, fmt.Errorf("unable to unmarshal response bytes into {{.TypeName}}Response: %w", err)
60+
}
61+
return &cast, nil
62+
}
63+
{{ end }}{{ range .ClusterRoutes }}
64+
type {{.TypeName}}Request struct { {{ if .HasParams }}
65+
Params {{.TypeName}}RequestParams{{ end }}{{ if .HasBody }}
66+
Body {{.TypeName}}RequestBody{{ end }}
67+
Headers http.Header
68+
}
69+
70+
func (c *{{$.ClientName}}) {{.TypeName}}(ctx context.Context, request {{.TypeName}}Request) (*{{.TypeName}}Response, error) { {{ if .HasParams }}
71+
params := url.Values{}{{ range .ParamValues }}
72+
params.Set("{{.Key}}", fmt.Sprintf("%v", request.Params.{{.FieldName}})){{ end }}{{ end }}{{ if .HasBody }}
73+
body, err := json.Marshal(request.Body)
74+
if err != nil {
75+
return nil, fmt.Errorf("unable to marshal body to JSON: %w", err)
76+
}{{ end }}
77+
resp, err := c.client.CustomRouteRequest(ctx, "", "", "", resource.CustomRouteRequestOptions{
78+
Path: "{{.Path}}",
79+
Verb: "{{.Method}}",{{ if .HasParams }}
80+
Query: params,{{ end }}{{ if .HasBody }}
81+
Body: io.NopCloser(bytes.NewReader(body)),{{ end }}
82+
Headers: request.Headers,
83+
})
84+
if err != nil {
85+
return nil, err
86+
}
87+
cast := {{.TypeName}}Response{}
88+
err = json.Unmarshal(resp, &cast)
89+
if err != nil {
90+
return nil, fmt.Errorf("unable to unmarshal response bytes into {{.TypeName}}Response: %w", err)
91+
}
92+
return &cast, nil
93+
}
94+
{{ end }}

codegen/templates/templates.go

Lines changed: 44 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,17 @@ var (
4141
},
4242
}
4343

44-
templateResourceObject, _ = template.ParseFS(templates, "resourceobject.tmpl")
45-
templateSchema, _ = template.ParseFS(templates, "schema.tmpl")
46-
templateCodec, _ = template.ParseFS(templates, "codec.tmpl")
47-
templateLineage, _ = template.ParseFS(templates, "lineage.tmpl")
48-
templateThemaCodec, _ = template.ParseFS(templates, "themacodec.tmpl")
49-
templateWrappedType, _ = template.ParseFS(templates, "wrappedtype.tmpl")
50-
templateTSType, _ = template.ParseFS(templates, "tstype.tmpl")
51-
templateConstants, _ = template.ParseFS(templates, "constants.tmpl")
52-
templateGoResourceClient, _ = template.ParseFS(templates, "resourceclient.tmpl")
53-
templateRuntimeObject, _ = template.ParseFS(templates, "runtimeobject.tmpl")
44+
templateResourceObject, _ = template.ParseFS(templates, "resourceobject.tmpl")
45+
templateSchema, _ = template.ParseFS(templates, "schema.tmpl")
46+
templateCodec, _ = template.ParseFS(templates, "codec.tmpl")
47+
templateLineage, _ = template.ParseFS(templates, "lineage.tmpl")
48+
templateThemaCodec, _ = template.ParseFS(templates, "themacodec.tmpl")
49+
templateWrappedType, _ = template.ParseFS(templates, "wrappedtype.tmpl")
50+
templateTSType, _ = template.ParseFS(templates, "tstype.tmpl")
51+
templateConstants, _ = template.ParseFS(templates, "constants.tmpl")
52+
templateGoResourceClient, _ = template.ParseFS(templates, "resourceclient.tmpl")
53+
templateGoVersionedRouteClient, _ = template.ParseFS(templates, "client.tmpl")
54+
templateRuntimeObject, _ = template.ParseFS(templates, "runtimeobject.tmpl")
5455

5556
templateBackendPluginRouter, _ = template.ParseFS(templates, "plugin/plugin.tmpl")
5657
templateBackendPluginResourceHandler, _ = template.ParseFS(templates, "plugin/handler_resource.tmpl")
@@ -588,19 +589,19 @@ type GoResourceClientMetadata struct {
588589
KindName string
589590
KindPrefix string
590591
Subresources []GoResourceClientSubresource
591-
CustomRoutes []GoResourceClientCustomRoute
592+
CustomRoutes []GoClientCustomRoute
592593
}
593594

594-
type GoResourceClientCustomRoute struct {
595+
type GoClientCustomRoute struct {
595596
TypeName string
596597
Path string
597598
Method string
598599
HasParams bool
599600
HasBody bool
600-
ParamValues []GoResourceClientParamValues
601+
ParamValues []GoCustomRouteParamValues
601602
}
602603

603-
type GoResourceClientParamValues struct {
604+
type GoCustomRouteParamValues struct {
604605
Key string
605606
FieldName string
606607
}
@@ -615,17 +616,44 @@ func WriteGoResourceClient(metadata GoResourceClientMetadata, out io.Writer) err
615616
slices.SortFunc(metadata.Subresources, func(a, b GoResourceClientSubresource) int {
616617
return strings.Compare(a.Subresource, b.Subresource)
617618
})
618-
slices.SortFunc(metadata.CustomRoutes, func(a, b GoResourceClientCustomRoute) int {
619+
slices.SortFunc(metadata.CustomRoutes, func(a, b GoClientCustomRoute) int {
619620
return strings.Compare(fmt.Sprintf("%s|%s", a.Path, a.Method), fmt.Sprintf("%s|%s", b.Path, b.Method))
620621
})
621622
for i := 0; i < len(metadata.CustomRoutes); i++ {
622-
slices.SortFunc(metadata.CustomRoutes[i].ParamValues, func(a GoResourceClientParamValues, b GoResourceClientParamValues) int {
623+
slices.SortFunc(metadata.CustomRoutes[i].ParamValues, func(a GoCustomRouteParamValues, b GoCustomRouteParamValues) int {
623624
return strings.Compare(a.FieldName, b.FieldName)
624625
})
625626
}
626627
return templateGoResourceClient.Execute(out, metadata)
627628
}
628629

630+
type GoGroupVersionClientMetadata struct {
631+
PackageName string
632+
ClientName string
633+
NamespacedRoutes []GoClientCustomRoute
634+
ClusterRoutes []GoClientCustomRoute
635+
}
636+
637+
func WriteGroupVersionClient(metadata GoGroupVersionClientMetadata, out io.Writer) error {
638+
slices.SortFunc(metadata.NamespacedRoutes, func(a, b GoClientCustomRoute) int {
639+
return strings.Compare(fmt.Sprintf("%s|%s", a.Path, a.Method), fmt.Sprintf("%s|%s", b.Path, b.Method))
640+
})
641+
slices.SortFunc(metadata.ClusterRoutes, func(a, b GoClientCustomRoute) int {
642+
return strings.Compare(fmt.Sprintf("%s|%s", a.Path, a.Method), fmt.Sprintf("%s|%s", b.Path, b.Method))
643+
})
644+
for i := 0; i < len(metadata.NamespacedRoutes); i++ {
645+
slices.SortFunc(metadata.NamespacedRoutes[i].ParamValues, func(a GoCustomRouteParamValues, b GoCustomRouteParamValues) int {
646+
return strings.Compare(a.FieldName, b.FieldName)
647+
})
648+
}
649+
for i := 0; i < len(metadata.ClusterRoutes); i++ {
650+
slices.SortFunc(metadata.ClusterRoutes[i].ParamValues, func(a GoCustomRouteParamValues, b GoCustomRouteParamValues) int {
651+
return strings.Compare(a.FieldName, b.FieldName)
652+
})
653+
}
654+
return templateGoVersionedRouteClient.Execute(out, metadata)
655+
}
656+
629657
type RuntimeObjectWrapperMetadata struct {
630658
PackageName string
631659
WrapperTypeName string

0 commit comments

Comments
 (0)