Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e76e4fe
feat(resourcecontext): add Build generator + wire /api/ai/resources G…
nadaverell May 17, 2026
8c9e0b0
fix(ai-handlers): wire PolicyReport index into ResourceContext.Build
nadaverell May 17, 2026
2aec8ec
fix(resourcecontext): tier-aware PolicySummary — basic emits counts only
nadaverell May 17, 2026
045097c
refactor(resourcecontext): consume topology.Relationships.ManagedBy +…
nadaverell May 17, 2026
99c878c
fix(security): RBAC preflight on /api/ai/resources GET handler
nadaverell May 17, 2026
d0a91c7
fix(resourcecontext): audit cross-Kind contamination + RunsOn fallbac…
nadaverell May 17, 2026
74ff1d5
fix(security): route group-qualified AI GET to dynamic cache to avoid…
nadaverell May 17, 2026
322d386
fix(resourcecontext): canonical kind + cross-group pseudo-kind for re…
nadaverell May 17, 2026
9f7887c
chore(resourcecontext): drop Hints prose projection from v1
nadaverell May 18, 2026
1b56e9b
docs(ai): declare /api/ai/* outside the OpenAPI spec, with reasoning
nadaverell May 18, 2026
bb3fe6d
fix(resourcecontext): pseudo-kind in fallback, swap CM/Secret reasons…
nadaverell May 18, 2026
444195a
chore(resourcecontext): drop speculative wire surface
nadaverell May 18, 2026
3e43759
fix(resourcecontext): address Bugbot findings on T6
nadaverell May 18, 2026
37983a1
fix(audit): match issue summary's nil-namespace guard for cluster-sco…
nadaverell May 18, 2026
3742ffa
fix: rebase fallout — group-aware FindingsFor + smoke-test import merge
nadaverell May 18, 2026
9d6658a
fix: group-aware audit + issue summary lookups
nadaverell May 18, 2026
7a1f006
fix(resourcecontext): normalize audit severity to issue vocabulary on…
nadaverell May 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion internal/issues/issues.go
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,21 @@ func condTypeReason(condType, reason string) string {
// Source-specific normalization
// ---------------------------------------------------------------------------

// resolveGroup returns the explicit group if set, else falls back to the
// built-in (Kind→Group) table. Some legacy Problem emission sites in
// k8s.DetectProblems still leave Group="" for built-in workloads
// (Deployment, StatefulSet, etc.) — without this fallback, the
// group-aware consumer (computeIssueSummaryForResource) would silently
// drop those rows when looking up by canonical group like "apps".
// Centralised here so the (Kind→Group) map lives in one place across
// packages (pkg/audit owns the table; this is a pass-through).
func resolveGroup(group, kind string) string {
if group != "" {
return group
}
return bp.GroupForBuiltinKind(kind)
}

func fromProblem(p k8s.Problem, now time.Time) Issue {
sev := SeverityWarning
if p.Severity == "critical" {
Expand All @@ -313,7 +328,7 @@ func fromProblem(p k8s.Problem, now time.Time) Issue {
Severity: sev,
Source: SourceProblem,
Kind: p.Kind,
Group: p.Group,
Group: resolveGroup(p.Group, p.Kind),
Namespace: p.Namespace,
Name: p.Name,
Reason: p.Reason,
Expand All @@ -333,6 +348,7 @@ func fromAudit(fin bp.Finding, now time.Time) Issue {
Severity: sev,
Source: SourceAudit,
Kind: fin.Kind,
Group: resolveGroup(fin.Group, fin.Kind),
Namespace: fin.Namespace,
Name: fin.Name,
Reason: fin.CheckID,
Expand Down Expand Up @@ -413,10 +429,19 @@ func fromWarningEvent(e *corev1.Event) Issue {
if first.IsZero() {
first = last
}
// Event.InvolvedObject carries apiVersion (group/version); split out
// the group so cross-group consumers don't collide when a Knative
// Service and a core Service share name+ns.
group, _, _ := strings.Cut(e.InvolvedObject.APIVersion, "/")
if e.InvolvedObject.APIVersion != "" && !strings.Contains(e.InvolvedObject.APIVersion, "/") {
// "v1" → core group "".
group = ""
}
return Issue{
Severity: SeverityWarning,
Source: SourceEvent,
Kind: e.InvolvedObject.Kind,
Group: resolveGroup(group, e.InvolvedObject.Kind),
Namespace: e.Namespace,
Name: e.InvolvedObject.Name,
Reason: e.Reason,
Expand Down
52 changes: 52 additions & 0 deletions internal/k8s/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"sync"

"github.com/skyhook-io/radar/pkg/k8score"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
fakeclientset "k8s.io/client-go/kubernetes/fake"
)

// InitTestResourceCache creates a resource cache from a fake or test client,
Expand Down Expand Up @@ -68,6 +70,56 @@ func InitTestResourceCache(client kubernetes.Interface) error {
return nil
}

// InitTestDynamicResourceCache wires the dynamic resource cache and discovery
// singletons against test fakes. Pass a dynamic client (typically from
// dynamicfake.NewSimpleDynamicClientWithCustomListKinds) and the set of
// APIResources to register in discovery. Each registered resource gets a GVR
// entry that group-qualified lookups (GetGVRWithGroup) and dynamic informers
// can resolve.
//
// Callers should defer ResetTestDynamicState — without it, the dynamic
// singletons leak into other tests that share TestMain state.
//
// This is intended for integration tests only.
func InitTestDynamicResourceCache(dynClient dynamic.Interface, resources []APIResource) error {
clientMu.Lock()
dynamicClient = dynClient
clientMu.Unlock()

// Bootstrap discovery from a fake clientset so NewResourceDiscovery has a
// non-nil discovery client; AddAPIResource then registers the test-only
// GVRs (e.g. serving.knative.dev/Service) the test depends on.
fakeDisc := fakeclientset.NewSimpleClientset().Discovery()
core, err := k8score.NewResourceDiscovery(fakeDisc)
if err != nil {
clientMu.Lock()
dynamicClient = nil
clientMu.Unlock()
return err
}
for _, r := range resources {
core.AddAPIResource(r)
}

discoveryMu.Lock()
resourceDiscovery = &ResourceDiscovery{ResourceDiscovery: core}
discoveryOnce = new(sync.Once)
discoveryOnce.Do(func() {})
discoveryMu.Unlock()

return InitDynamicResourceCache(nil)
}

// ResetTestDynamicState tears down the dynamic cache + discovery singletons
// and clears the dynamic client. Pairs with InitTestDynamicResourceCache.
func ResetTestDynamicState() {
ResetDynamicResourceCache()
ResetResourceDiscovery()
clientMu.Lock()
dynamicClient = nil
clientMu.Unlock()
}

// SetTestContextName is a test-only helper that overrides the package-level
// kubeconfig context name. Used by tests that exercise per-context state
// (e.g. namespace preferences) without needing to spin up a real client.
Expand Down
Loading
Loading