Skip to content

Commit 8651f7c

Browse files
committed
fix(resource-proxy): support API group discovery requests
Signed-off-by: immanuwell <pchpr.00@list.ru>
1 parent 4a2c614 commit 8651f7c

5 files changed

Lines changed: 89 additions & 3 deletions

File tree

agent/resource.go

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -434,20 +434,35 @@ func (a *Agent) getAvailableResources(ctx context.Context, gvr schema.GroupVersi
434434
return res, err
435435
}
436436

437-
// getAvailableAPIs retrieves a list of available APIs for a given group and version.
437+
// getAvailableAPIs retrieves discovery data for a given group and version.
438438
// If group and version are empty, all available APIs are returned.
439439
// If group is empty and version is not, all APIs for the given version are returned.
440-
// If group and version are not empty, all APIs for the given group and version are returned.
440+
// If group is not empty and version is empty, metadata for the given API group is returned.
441+
// If group and version are both set, all APIs for the given group and version are returned.
441442
//
442443
// It does not yet support the new aggregated API.
443444
func (a *Agent) getAvailableAPIs(ctx context.Context, group, version string) (*unstructured.Unstructured, error) {
444445
groupVersion := fmt.Sprintf("%s/%s", group, version)
445446
var groupList *v1.APIGroupList
447+
var groupInfo *v1.APIGroup
446448
var resourceList *v1.APIResourceList
447449
var err error
448450

449451
if group == "" && version == "" {
450452
groupList, err = a.kubeClient.Clientset.Discovery().ServerGroups()
453+
} else if group != "" && version == "" {
454+
groupList, err = a.kubeClient.Clientset.Discovery().ServerGroups()
455+
if err == nil {
456+
for i := range groupList.Groups {
457+
if groupList.Groups[i].Name == group {
458+
groupInfo = &groupList.Groups[i]
459+
break
460+
}
461+
}
462+
if groupInfo == nil {
463+
err = fmt.Errorf("api group %s not found", group)
464+
}
465+
}
451466
} else if group == "" && version != "" {
452467
resourceList, err = a.kubeClient.Clientset.Discovery().ServerResourcesForGroupVersion(version)
453468
} else {
@@ -458,7 +473,9 @@ func (a *Agent) getAvailableAPIs(ctx context.Context, group, version string) (*u
458473
}
459474

460475
var obj map[string]any
461-
if groupList != nil {
476+
if groupInfo != nil {
477+
obj, err = runtime.DefaultUnstructuredConverter.ToUnstructured(groupInfo)
478+
} else if groupList != nil {
462479
obj, err = runtime.DefaultUnstructuredConverter.ToUnstructured(groupList)
463480
} else if resourceList != nil {
464481
obj, err = runtime.DefaultUnstructuredConverter.ToUnstructured(resourceList)

agent/resource_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1289,6 +1289,20 @@ func Test_getAvailableAPIs(t *testing.T) {
12891289
assert.Contains(t, firstVersion, "version")
12901290
})
12911291

1292+
t.Run("Successfully get API group metadata when only group is specified", func(t *testing.T) {
1293+
agent := &Agent{
1294+
context: context.Background(),
1295+
kubeClient: kube.NewDynamicFakeClient(),
1296+
}
1297+
1298+
result, err := agent.getAvailableAPIs(context.Background(), "apps", "")
1299+
assert.NoError(t, err)
1300+
assert.NotNil(t, result)
1301+
assert.Equal(t, "apps", result.Object["name"])
1302+
assert.Contains(t, result.Object, "versions")
1303+
assert.Contains(t, result.Object, "preferredVersion")
1304+
})
1305+
12921306
t.Run("Returns error for non-existent API group", func(t *testing.T) {
12931307
agent := &Agent{
12941308
context: context.Background(),
@@ -1299,6 +1313,17 @@ func Test_getAvailableAPIs(t *testing.T) {
12991313
assert.Error(t, err)
13001314
assert.Nil(t, result)
13011315
})
1316+
1317+
t.Run("Returns error for non-existent API group without version", func(t *testing.T) {
1318+
agent := &Agent{
1319+
context: context.Background(),
1320+
kubeClient: kube.NewDynamicFakeClient(),
1321+
}
1322+
1323+
result, err := agent.getAvailableAPIs(context.Background(), "nonexistent", "")
1324+
assert.Error(t, err)
1325+
assert.Nil(t, result)
1326+
})
13021327
}
13031328

13041329
func Test_processIncomingDeleteResourceRequest(t *testing.T) {

principal/resource.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import (
3535
// capture groups. It also supports Kubernetes subresources.
3636
const resourceRequestRegexp = `^/(?:api|apis|(?:api|apis/(?P<group>[^\/]+))/(?P<version>v[^\/]+)(?:/(?:namespaces/(?P<namespace>[^\/]+)/)?)?(?:(?P<resource>[^\/]+)(?:/(?P<name>[^\/]+)(?:/(?P<subresource>[^\/]+))?)?)?)$`
3737

38+
// resourceGroupRequestRegexp matches API group discovery requests such as
39+
// /apis/apps, which do not include an explicit version.
40+
const resourceGroupRequestRegexp = `^/apis/(?P<group>[^\/]+)$`
41+
3842
// requestTimeout is the timeout that's being applied to requests for any live
3943
// resource.
4044
//

principal/resource_regex_test.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,3 +198,37 @@ func TestResourceRequestRegexp(t *testing.T) {
198198
})
199199
}
200200
}
201+
202+
func TestResourceGroupRequestRegexp(t *testing.T) {
203+
re, err := regexp.Compile(resourceGroupRequestRegexp)
204+
require.NoError(t, err)
205+
206+
tests := []struct {
207+
path string
208+
group string
209+
shouldHit bool
210+
}{
211+
{path: "/apis/apps", group: "apps", shouldHit: true},
212+
{path: "/apis/networking.k8s.io", group: "networking.k8s.io", shouldHit: true},
213+
{path: "/apis", shouldHit: false},
214+
{path: "/apis/apps/v1", shouldHit: false},
215+
}
216+
217+
for _, tt := range tests {
218+
t.Run(tt.path, func(t *testing.T) {
219+
matches := re.FindStringSubmatch(tt.path)
220+
if !tt.shouldHit {
221+
assert.Nil(t, matches)
222+
return
223+
}
224+
225+
require.NotNil(t, matches)
226+
names := re.SubexpNames()
227+
for i, name := range names {
228+
if name == "group" {
229+
assert.Equal(t, tt.group, matches[i])
230+
}
231+
}
232+
})
233+
}
234+
}

principal/server.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,12 @@ func NewServer(ctx context.Context, kubeClient *kube.KubernetesClient, namespace
541541
if s.resourceProxyEnabled {
542542
// TODO(jannfis): Enable fetching APIs and resource counts
543543
s.resourceProxy, err = resourceproxy.New(s.resourceProxyListenAddr,
544+
// Kubernetes API group discovery, for example /apis/apps
545+
resourceproxy.WithRequestMatcher(
546+
resourceGroupRequestRegexp,
547+
[]string{"get"},
548+
s.processResourceRequest,
549+
),
544550
// For matching resource requests from the Argo CD API
545551
resourceproxy.WithRequestMatcher(
546552
resourceRequestRegexp,

0 commit comments

Comments
 (0)