diff --git a/README.md b/README.md index 559d5d1..0037916 100644 --- a/README.md +++ b/README.md @@ -26,11 +26,6 @@ spec: url: oci://ghcr.io/gimlet-io/capacitor-manifests ref: semver: ">=0.1.0" - verify: - provider: cosign - matchOIDCIdentity: - - issuer: "https://token.actions.githubusercontent.com" - subject: "^https://github.com/gimlet-io/capacitor.*$" --- apiVersion: kustomize.toolkit.fluxcd.io/v1 kind: Kustomization diff --git a/cmd/capacitor/main.go b/cmd/capacitor/main.go index 86ac9f0..86b9cf0 100644 --- a/cmd/capacitor/main.go +++ b/cmd/capacitor/main.go @@ -51,6 +51,10 @@ func main() { runController(err, ociRepositoryController, stopCh) bucketController, err := controllers.BucketController(client, dynamicClient, clientHub) runController(err, bucketController, stopCh) + helmRepositoryController, err := controllers.HelmRepositoryController(client, dynamicClient, clientHub) + runController(err, helmRepositoryController, stopCh) + helmChartController, err := controllers.HelmChartController(client, dynamicClient, clientHub) + runController(err, helmChartController, stopCh) kustomizationController, err := controllers.KustomizeController(client, dynamicClient, clientHub) runController(err, kustomizationController, stopCh) helmReleaseController, err := controllers.HelmReleaseController(client, dynamicClient, clientHub) diff --git a/deploy/k8s/rbac.yaml b/deploy/k8s/rbac.yaml index d1e7c28..309aeb9 100644 --- a/deploy/k8s/rbac.yaml +++ b/deploy/k8s/rbac.yaml @@ -34,6 +34,8 @@ rules: - gitrepositories - ocirepositories - buckets + - helmrepositories + - helmcharts - kustomizations - helmreleases verbs: diff --git a/pkg/controllers/helmChartController.go b/pkg/controllers/helmChartController.go new file mode 100644 index 0000000..0e8e9d1 --- /dev/null +++ b/pkg/controllers/helmChartController.go @@ -0,0 +1,54 @@ +package controllers + +import ( + "encoding/json" + + "github.com/gimlet-io/capacitor/pkg/flux" + "github.com/gimlet-io/capacitor/pkg/streaming" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" +) + +var helmChartResource = schema.GroupVersionResource{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Resource: "helmcharts", +} + +func HelmChartController( + client *kubernetes.Clientset, + dynamicClient *dynamic.DynamicClient, + clientHub *streaming.ClientHub, +) (*Controller, error) { + return NewDynamicController( + "helmcharts.source.toolkit.fluxcd.io", + dynamicClient, + helmChartResource, + func(informerEvent Event, objectMeta metav1.ObjectMeta, obj interface{}) error { + switch informerEvent.eventType { + case "create": + fallthrough + case "update": + fallthrough + case "delete": + fluxState, err := flux.State(client, dynamicClient) + if err != nil { + logrus.Warnf("could not get flux state: %s", err) + return nil + } + fluxStateBytes, err := json.Marshal(streaming.Envelope{ + Type: streaming.FLUX_STATE_RECEIVED, + Payload: fluxState, + }) + if err != nil { + logrus.Warnf("could not marshal event: %s", err) + return nil + } + clientHub.Broadcast <- fluxStateBytes + } + return nil + }) +} diff --git a/pkg/controllers/helmRepositoryController.go b/pkg/controllers/helmRepositoryController.go new file mode 100644 index 0000000..a6d92bd --- /dev/null +++ b/pkg/controllers/helmRepositoryController.go @@ -0,0 +1,54 @@ +package controllers + +import ( + "encoding/json" + + "github.com/gimlet-io/capacitor/pkg/flux" + "github.com/gimlet-io/capacitor/pkg/streaming" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" +) + +var helmRepositoryResource = schema.GroupVersionResource{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Resource: "helmrepositories", +} + +func HelmRepositoryController( + client *kubernetes.Clientset, + dynamicClient *dynamic.DynamicClient, + clientHub *streaming.ClientHub, +) (*Controller, error) { + return NewDynamicController( + "helmrepositories.source.toolkit.fluxcd.io", + dynamicClient, + helmRepositoryResource, + func(informerEvent Event, objectMeta metav1.ObjectMeta, obj interface{}) error { + switch informerEvent.eventType { + case "create": + fallthrough + case "update": + fallthrough + case "delete": + fluxState, err := flux.State(client, dynamicClient) + if err != nil { + logrus.Warnf("could not get flux state: %s", err) + return nil + } + fluxStateBytes, err := json.Marshal(streaming.Envelope{ + Type: streaming.FLUX_STATE_RECEIVED, + Payload: fluxState, + }) + if err != nil { + logrus.Warnf("could not marshal event: %s", err) + return nil + } + clientHub.Broadcast <- fluxStateBytes + } + return nil + }) +} diff --git a/pkg/flux/flux.go b/pkg/flux/flux.go index 259c08c..f0e1bfc 100644 --- a/pkg/flux/flux.go +++ b/pkg/flux/flux.go @@ -66,6 +66,18 @@ var ( Version: "v2beta1", Resource: "helmreleases", } + + helmRepositoryGVR = schema.GroupVersionResource{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Resource: "helmrepositories", + } + + helmChartGVR = schema.GroupVersionResource{ + Group: "source.toolkit.fluxcd.io", + Version: "v1beta2", + Resource: "helmcharts", + } ) func helmServices(dc *dynamic.DynamicClient) ([]Service, error) { @@ -336,12 +348,14 @@ func helmStatusWithResources( func State(c *kubernetes.Clientset, dc *dynamic.DynamicClient) (*FluxState, error) { fluxState := &FluxState{ - GitRepositories: []sourcev1.GitRepository{}, - OCIRepositories: []sourcev1beta2.OCIRepository{}, - Buckets: []sourcev1beta2.Bucket{}, - Kustomizations: []kustomizationv1.Kustomization{}, - HelmReleases: []helmv2beta2.HelmRelease{}, - FluxServices: []Service{}, + GitRepositories: []sourcev1.GitRepository{}, + OCIRepositories: []sourcev1beta2.OCIRepository{}, + Buckets: []sourcev1beta2.Bucket{}, + Kustomizations: []kustomizationv1.Kustomization{}, + HelmReleases: []helmv2beta2.HelmRelease{}, + HelmRepositories: []sourcev1beta2.HelmRepository{}, + HelmCharts: []sourcev1beta2.HelmChart{}, + FluxServices: []Service{}, } gitRepositories, err := dc.Resource(gitRepositoryGVR). @@ -442,6 +456,38 @@ func State(c *kubernetes.Clientset, dc *dynamic.DynamicClient) (*FluxState, erro fluxState.HelmReleases = append(fluxState.HelmReleases, helmRelease) } + helmRepositories, err := dc.Resource(helmRepositoryGVR). + Namespace(""). + List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + for _, h := range helmRepositories.Items { + unstructured := h.UnstructuredContent() + var helmRepository sourcev1beta2.HelmRepository + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured, &helmRepository) + if err != nil { + return nil, err + } + fluxState.HelmRepositories = append(fluxState.HelmRepositories, helmRepository) + } + + helmCharts, err := dc.Resource(helmChartGVR). + Namespace(""). + List(context.TODO(), metav1.ListOptions{}) + if err != nil { + return nil, err + } + for _, h := range helmCharts.Items { + unstructured := h.UnstructuredContent() + var helmChart sourcev1beta2.HelmChart + err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstructured, &helmChart) + if err != nil { + return nil, err + } + fluxState.HelmCharts = append(fluxState.HelmCharts, helmChart) + } + fluxServices, err := fluxServicesWithDetails(c) if err != nil { return nil, err diff --git a/pkg/flux/reconcile.go b/pkg/flux/reconcile.go index 2933d88..36b31cc 100644 --- a/pkg/flux/reconcile.go +++ b/pkg/flux/reconcile.go @@ -85,6 +85,18 @@ func NewReconcileCommand(resource string) *reconcileCommand { groupVersion: sourcev1beta2.GroupVersion, kind: sourcev1beta2.BucketKind, } + case sourcev1beta2.HelmRepositoryKind: + return &reconcileCommand{ + object: helmRepositoryAdapter{&sourcev1beta2.HelmRepository{}}, + groupVersion: sourcev1beta2.GroupVersion, + kind: sourcev1beta2.HelmRepositoryKind, + } + case sourcev1beta2.HelmChartKind: + return &reconcileCommand{ + object: helmChartAdapter{&sourcev1beta2.HelmChart{}}, + groupVersion: sourcev1beta2.GroupVersion, + kind: sourcev1beta2.HelmChartKind, + } } return nil diff --git a/pkg/flux/source.go b/pkg/flux/source.go index 888404d..520df93 100644 --- a/pkg/flux/source.go +++ b/pkg/flux/source.go @@ -205,3 +205,43 @@ func (a bucketListAdapter) item(i int) suspendable { func (a bucketListAdapter) resumeItem(i int) resumable { return &bucketAdapter{&a.BucketList.Items[i]} } + +type helmRepositoryAdapter struct { + *sourcev1beta2.HelmRepository +} + +func (a helmRepositoryAdapter) asClientObject() client.Object { + return a.HelmRepository +} + +func (obj helmRepositoryAdapter) isSuspended() bool { + return obj.HelmRepository.Spec.Suspend +} + +func (obj helmRepositoryAdapter) lastHandledReconcileRequest() string { + return obj.Status.GetLastHandledReconcileRequest() +} + +func (obj helmRepositoryAdapter) successMessage() string { + return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) +} + +type helmChartAdapter struct { + *sourcev1beta2.HelmChart +} + +func (a helmChartAdapter) asClientObject() client.Object { + return a.HelmChart +} + +func (obj helmChartAdapter) isSuspended() bool { + return obj.HelmChart.Spec.Suspend +} + +func (obj helmChartAdapter) lastHandledReconcileRequest() string { + return obj.Status.GetLastHandledReconcileRequest() +} + +func (obj helmChartAdapter) successMessage() string { + return fmt.Sprintf("fetched revision %s", obj.Status.Artifact.Revision) +} diff --git a/pkg/flux/types.go b/pkg/flux/types.go index 8149cbd..fade128 100644 --- a/pkg/flux/types.go +++ b/pkg/flux/types.go @@ -13,12 +13,14 @@ import ( ) type FluxState struct { - GitRepositories []sourcev1.GitRepository `json:"gitRepositories"` - OCIRepositories []sourcev1beta2.OCIRepository `json:"ociRepositories"` - Buckets []sourcev1beta2.Bucket `json:"buckets"` - Kustomizations []kustomizationv1.Kustomization `json:"kustomizations"` - HelmReleases []helmv2beta2.HelmRelease `json:"helmReleases"` - FluxServices []Service `json:"fluxServices"` + GitRepositories []sourcev1.GitRepository `json:"gitRepositories"` + OCIRepositories []sourcev1beta2.OCIRepository `json:"ociRepositories"` + Buckets []sourcev1beta2.Bucket `json:"buckets"` + Kustomizations []kustomizationv1.Kustomization `json:"kustomizations"` + HelmReleases []helmv2beta2.HelmRelease `json:"helmReleases"` + HelmRepositories []sourcev1beta2.HelmRepository `json:"helmRepositories"` + HelmCharts []sourcev1beta2.HelmChart `json:"helmCharts"` + FluxServices []Service `json:"fluxServices"` } type Service struct { diff --git a/web/src/App.js b/web/src/App.js index 78e4f27..85fa9a5 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -38,27 +38,6 @@ function App() { localStorage.setItem("filters", JSON.stringify(filters)); }, [filters]); - const addFilter = (filter) => { - setFilters([...filters, filter]); - } - - const filterValueByProperty = (property) => { - const filter = filters.find(f => f.property === property) - if (!filter) { - return "" - } - - return filter.value - } - - const deleteFilter = (filter) => { - setFilters(filters.filter(f => f.property !== filter.property)) - } - - const resetFilters = () => { - setFilters([]) - } - return ( <> @@ -66,12 +45,10 @@ function App() {
-
diff --git a/web/src/ExpandedFooter.jsx b/web/src/ExpandedFooter.jsx index a6717ed..b11d292 100644 --- a/web/src/ExpandedFooter.jsx +++ b/web/src/ExpandedFooter.jsx @@ -39,7 +39,7 @@ export function ExpandedFooter(props) { } {selected === "Sources" && - + } {selected === "Flux Runtime" && diff --git a/web/src/FilterBar.js b/web/src/FilterBar.js index bbc7358..066d3c4 100644 --- a/web/src/FilterBar.js +++ b/web/src/FilterBar.js @@ -1,7 +1,28 @@ import React, { useState, useRef, useEffect } from 'react'; import { FunnelIcon, XMarkIcon } from '@heroicons/react/24/outline' -function FilterBar({ filters, addFilter, deleteFilter, resetFilters, filterValueByProperty }) { +function FilterBar({ properties = [], filters, change }) { + const addFilter = (filter) => { + change([...filters, filter]); + } + + const filterValueByProperty = (property) => { + const filter = filters.find(f => f.property === property) + if (!filter) { + return "" + } + + return filter.value + } + + const deleteFilter = (filter) => { + change(filters.filter(f => f.property !== filter.property)) + } + + const resetFilters = () => { + change([]) + } + return (
@@ -10,7 +31,7 @@ function FilterBar({ filters, addFilter, deleteFilter, resetFilters, filterValue {filters.map(filter => ( ))} - +
  @@ -29,7 +50,7 @@ function Filter(props) { const { filter } = props; return (