Skip to content

Commit bb05ac3

Browse files
authored
feat: iterating on fluxcd support (#300)
* feat: HR events * tinkering * tinkering * feat: clear logs * feat: stalled hr badge * ux * ui thing * fix: resetting filters if you change resourec types * fix: restore url state
1 parent 597b384 commit bb05ac3

File tree

11 files changed

+594
-153
lines changed

11 files changed

+594
-153
lines changed

cli/pkg/server/k8s_proxy.go

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package server
55

66
import (
77
"bytes"
8+
"context"
89
"encoding/json"
910
"fmt"
1011
"io"
@@ -13,16 +14,21 @@ import (
1314
"net/http/httputil"
1415
"net/url"
1516
"strings"
17+
"sync"
1618

1719
"github.com/gimlet-io/capacitor/pkg/kubernetes"
1820
"github.com/labstack/echo/v4"
21+
"k8s.io/client-go/discovery"
1922
"k8s.io/client-go/rest"
2023
)
2124

2225
// KubernetesProxy handles proxying requests to the Kubernetes API
2326
type KubernetesProxy struct {
24-
k8sClient *kubernetes.Client
25-
proxy *httputil.ReverseProxy
27+
k8sClient *kubernetes.Client
28+
proxy *httputil.ReverseProxy
29+
fluxAPIPaths map[string]string // Cache for discovered Flux API paths (kind -> API path template)
30+
fluxAPIPathsMu sync.RWMutex // Mutex for thread-safe access to fluxAPIPaths
31+
fluxAPIDiscovery sync.Once // Ensures discovery happens only once
2632
}
2733

2834
// NewKubernetesProxy creates a new KubernetesProxy
@@ -105,8 +111,9 @@ func NewKubernetesProxy(k8sClient *kubernetes.Client) (*KubernetesProxy, error)
105111
}
106112

107113
return &KubernetesProxy{
108-
k8sClient: k8sClient,
109-
proxy: proxy,
114+
k8sClient: k8sClient,
115+
proxy: proxy,
116+
fluxAPIPaths: make(map[string]string),
110117
}, nil
111118
}
112119

@@ -172,3 +179,138 @@ func removeManagedFieldsFromAny(v *interface{}) {
172179
// primitives: nothing to do
173180
}
174181
}
182+
183+
// discoverFluxAPIPaths discovers Flux API paths using Kubernetes discovery client
184+
// Returns a map of resource kind to API path template (e.g., "Kustomization" -> "/apis/kustomize.toolkit.fluxcd.io/v1/namespaces/%s/kustomizations/%s")
185+
// Uses sync.Once to ensure discovery happens only once per proxy instance
186+
func (p *KubernetesProxy) discoverFluxAPIPaths() (map[string]string, error) {
187+
var discoveryErr error
188+
189+
// Use sync.Once to ensure discovery happens only once
190+
p.fluxAPIDiscovery.Do(func() {
191+
// Create discovery client
192+
discoveryClient, err := discovery.NewDiscoveryClientForConfig(p.k8sClient.Config)
193+
if err != nil {
194+
discoveryErr = fmt.Errorf("failed to create discovery client: %w", err)
195+
return
196+
}
197+
198+
// Get API groups
199+
apiGroups, err := discoveryClient.ServerGroups()
200+
if err != nil {
201+
discoveryErr = fmt.Errorf("failed to get API groups: %w", err)
202+
return
203+
}
204+
205+
// Map of Flux API group prefixes to their resource kinds
206+
fluxGroupKinds := map[string][]string{
207+
"kustomize.toolkit.fluxcd.io": {"Kustomization"},
208+
"helm.toolkit.fluxcd.io": {"HelmRelease"},
209+
"source.toolkit.fluxcd.io": {"GitRepository", "HelmRepository", "HelmChart", "OCIRepository", "Bucket"},
210+
"notification.toolkit.fluxcd.io": {"Alert", "Provider", "Receiver"},
211+
"image.toolkit.fluxcd.io": {"ImagePolicy", "ImageRepository", "ImageUpdate"},
212+
"infra.contrib.fluxcd.io": {"Terraform"},
213+
}
214+
215+
// Map of resource kind to plural form
216+
kindToPlural := map[string]string{
217+
"Kustomization": "kustomizations",
218+
"HelmRelease": "helmreleases",
219+
"GitRepository": "gitrepositories",
220+
"HelmRepository": "helmrepositories",
221+
"HelmChart": "helmcharts",
222+
"OCIRepository": "ocirepositories",
223+
"Bucket": "buckets",
224+
"Alert": "alerts",
225+
"Provider": "providers",
226+
"Receiver": "receivers",
227+
"ImagePolicy": "imagepolicies",
228+
"ImageRepository": "imagerepositories",
229+
"ImageUpdate": "imageupdateautomations",
230+
"Terraform": "terraforms",
231+
}
232+
233+
discoveredPaths := make(map[string]string)
234+
235+
// Iterate through API groups to find Flux groups
236+
for _, group := range apiGroups.Groups {
237+
for groupPrefix, kinds := range fluxGroupKinds {
238+
if strings.Contains(group.Name, groupPrefix) {
239+
// Find the preferred version
240+
preferredVersion := group.PreferredVersion.Version
241+
if preferredVersion == "" && len(group.Versions) > 0 {
242+
preferredVersion = group.Versions[0].Version
243+
}
244+
245+
// Get resources for this group version
246+
groupVersion := fmt.Sprintf("%s/%s", group.Name, preferredVersion)
247+
resources, err := discoveryClient.ServerResourcesForGroupVersion(groupVersion)
248+
if err != nil {
249+
log.Printf("Warning: failed to get resources for %s: %v", groupVersion, err)
250+
continue
251+
}
252+
253+
// Map each kind to its API path
254+
for _, kind := range kinds {
255+
plural, ok := kindToPlural[kind]
256+
if !ok {
257+
log.Printf("Warning: no plural form found for kind %s", kind)
258+
continue
259+
}
260+
261+
// Check if this resource exists in the discovered resources
262+
found := false
263+
for _, resource := range resources.APIResources {
264+
if resource.Name == plural {
265+
found = true
266+
break
267+
}
268+
}
269+
270+
if found {
271+
// Build API path template: /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name}
272+
apiPath := fmt.Sprintf("/apis/%s/%s/namespaces/%%s/%s/%%s", group.Name, preferredVersion, plural)
273+
discoveredPaths[kind] = apiPath
274+
}
275+
}
276+
}
277+
}
278+
}
279+
280+
// Cache the results
281+
p.fluxAPIPathsMu.Lock()
282+
p.fluxAPIPaths = discoveredPaths
283+
p.fluxAPIPathsMu.Unlock()
284+
})
285+
286+
// Return error if discovery failed
287+
if discoveryErr != nil {
288+
return nil, discoveryErr
289+
}
290+
291+
// Return cached results (thread-safe read)
292+
p.fluxAPIPathsMu.RLock()
293+
defer p.fluxAPIPathsMu.RUnlock()
294+
295+
// Return a copy to prevent external modification
296+
result := make(map[string]string, len(p.fluxAPIPaths))
297+
for k, v := range p.fluxAPIPaths {
298+
result[k] = v
299+
}
300+
return result, nil
301+
}
302+
303+
// getFluxAPIPath returns the API path for a Flux resource kind, discovering it if necessary
304+
func (p *KubernetesProxy) getFluxAPIPath(ctx context.Context, kind string) (string, error) {
305+
paths, err := p.discoverFluxAPIPaths()
306+
if err != nil {
307+
return "", err
308+
}
309+
310+
apiPath, found := paths[kind]
311+
if !found {
312+
return "", fmt.Errorf("flux resource kind %s not found in discovered API paths", kind)
313+
}
314+
315+
return apiPath, nil
316+
}

0 commit comments

Comments
 (0)