Skip to content

Commit

Permalink
feat: install ephermeral reports api group
Browse files Browse the repository at this point in the history
Signed-off-by: Vishal Choudhary <[email protected]>
  • Loading branch information
vishal-chdhry committed Jan 28, 2024
1 parent 926e47b commit cf8e246
Show file tree
Hide file tree
Showing 10 changed files with 803 additions and 12 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ codegen-openapi: $(PACKAGE_SHIM) $(OPENAPI_GEN) ## Generate openapi
-i k8s.io/apimachinery/pkg/types \
-i k8s.io/api/core/v1 \
-i sigs.k8s.io/wg-policy-prototypes/policy-report/pkg/api/wgpolicyk8s.io/v1alpha2 \
-i github.com/kyverno/kyverno/api/reports/v1 \
-p ./pkg/api/generated/openapi \
-O zz_generated.openapi \
-h ./.hack/boilerplate.go.txt
Expand Down
1 change: 1 addition & 0 deletions charts/reports-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ helm install reports-server --namespace reports-server --create-namespace report
| postgresql.enabled | bool | `true` | Deploy postgresql dependency chart |
| postgresql.auth.postgresPassword | string | `"reports"` | |
| postgresql.auth.database | string | `"reportsdb"` | |
| ephemeralReportsStorage.enabled | bool | `true` | Store ephemeral reports in reports-server |
| nameOverride | string | `""` | Name override |
| fullnameOverride | string | `""` | Full name override |
| replicaCount | int | `1` | Number of pod replicas |
Expand Down
20 changes: 20 additions & 0 deletions charts/reports-server/templates/api-service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,23 @@ spec:
namespace: {{ $.Release.Namespace }}
version: v1alpha2
versionPriority: 100

{{- if .Values.ephemeralReportsStorage.enabled }}
---
apiVersion: apiregistration.k8s.io/v1
kind: APIService
metadata:
name: v1.reports.kyverno.io
namespace: {{ $.Release.Namespace }}
labels:
{{- include "reports-server.labels" . | nindent 4 }}
spec:
group: reports.kyverno.io
groupPriorityMinimum: 100
insecureSkipTLSVerify: true
service:
name: {{ include "reports-server.fullname" . }}
namespace: {{ $.Release.Namespace }}
version: v1
versionPriority: 100
{{- end }}
5 changes: 5 additions & 0 deletions charts/reports-server/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ postgresql:

database: reportsdb

ephemeralReportsStorage:

# -- Store ephemeral reports in reports-server
enabled: true

# -- Name override
nameOverride: ""

Expand Down
331 changes: 331 additions & 0 deletions pkg/api/cephr.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
package api

import (
"context"
"fmt"
"slices"
"strconv"

reportsv1 "github.com/kyverno/kyverno/api/reports/v1"
"github.com/kyverno/reports-server/pkg/storage"
errorpkg "github.com/pkg/errors"
"k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apimachinery/pkg/watch"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/klog/v2"
)

type cephrStore struct {
broadcaster *watch.Broadcaster
store storage.Interface
}

func ClusterEphemeralReportStore(store storage.Interface) API {
broadcaster := watch.NewBroadcaster(1000, watch.WaitIfChannelFull)

return &cephrStore{
broadcaster: broadcaster,
store: store,
}
}

func (c *cephrStore) New() runtime.Object {
return &reportsv1.ClusterEphemeralReport{}
}

func (c *cephrStore) Destroy() {
}

func (c *cephrStore) Kind() string {
return "ClusterEphemeralReport"
}

func (c *cephrStore) NewList() runtime.Object {
return &reportsv1.ClusterEphemeralReportList{}
}

func (c *cephrStore) List(ctx context.Context, options *metainternalversion.ListOptions) (runtime.Object, error) {
labelSelector := labels.Everything()
// fieldSelector := fields.Everything() // TODO: Field selectors
if options != nil {
if options.LabelSelector != nil {
labelSelector = options.LabelSelector
}
// if options.FieldSelector != nil {
// fieldSelector = options.FieldSelector
// }
}
list, err := c.listCephr()
if err != nil {
return nil, errors.NewBadRequest("failed to list resource clusterephemeralreport")
}

// if labelSelector.String() == labels.Everything().String() {
// return list, nil
// }

cephrList := &reportsv1.ClusterEphemeralReportList{
Items: make([]reportsv1.ClusterEphemeralReport, 0),
}
for _, cephr := range list.Items {
if cephr.Labels == nil {
return list, nil
}
if labelSelector.Matches(labels.Set(cephr.Labels)) {
cephrList.Items = append(cephrList.Items, cephr)
}
}

return cephrList, nil
}

func (c *cephrStore) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
report, err := c.getCephr(name)
if err != nil || report == nil {
return nil, errors.NewNotFound(reportsv1.Resource("clusterephemeralreports"), name)
}
return report, nil
}

func (c *cephrStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
isDryRun := slices.Contains(options.DryRun, "All")

err := createValidation(ctx, obj)
if err != nil {
switch options.FieldValidation {
case "Ignore":
case "Warn":
// return &admissionv1.AdmissionResponse{
// Allowed: false,
// Warnings: []string{err.Error()},
// }, nil
case "Strict":
return nil, err
}
}

cephr, ok := obj.(*reportsv1.ClusterEphemeralReport)
if !ok {
return nil, errors.NewBadRequest("failed to validate cluster ephemeral report")
}

if !isDryRun {
if err := c.createCephr(cephr); err != nil {
return nil, errors.NewBadRequest(fmt.Sprintf("cannot create cluster ephemeral report: %s", err.Error()))
}
if err := c.broadcaster.Action(watch.Added, obj); err != nil {
klog.ErrorS(err, "failed to broadcast event")
}
}

return obj, nil
}

func (c *cephrStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
isDryRun := slices.Contains(options.DryRun, "All")

if forceAllowCreate {
oldObj, _ := c.getCephr(name)
updatedObject, _ := objInfo.UpdatedObject(ctx, oldObj)
cephr := updatedObject.(*reportsv1.ClusterEphemeralReport)
if err := c.updateCephr(cephr, true); err != nil {
klog.ErrorS(err, "failed to update resource")
}
if err := c.broadcaster.Action(watch.Added, updatedObject); err != nil {
klog.ErrorS(err, "failed to broadcast event")
}
return updatedObject, true, nil
}

oldObj, err := c.getCephr(name)
if err != nil {
return nil, false, err
}

updatedObject, err := objInfo.UpdatedObject(ctx, oldObj)
if err != nil {
return nil, false, err
}
err = updateValidation(ctx, updatedObject, oldObj)
if err != nil {
switch options.FieldValidation {
case "Ignore":
case "Warn":
// return &admissionv1.AdmissionResponse{
// Allowed: false,
// Warnings: []string{err.Error()},
// }, nil
case "Strict":
return nil, false, err
}
}

cephr, ok := updatedObject.(*reportsv1.ClusterEphemeralReport)
if !ok {
return nil, false, errors.NewBadRequest("failed to validate cluster ephemeral report")
}

if !isDryRun {
if err := c.createCephr(cephr); err != nil {
return nil, false, errors.NewBadRequest(fmt.Sprintf("cannot create cluster ephemeral report: %s", err.Error()))
}
if err := c.broadcaster.Action(watch.Modified, updatedObject); err != nil {
klog.ErrorS(err, "failed to broadcast event")
}
}

return updatedObject, true, nil
}

func (c *cephrStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
isDryRun := slices.Contains(options.DryRun, "All")

cephr, err := c.getCephr(name)
if err != nil {
klog.ErrorS(err, "Failed to find cephrs", "name", name)
return nil, false, errors.NewNotFound(reportsv1.Resource("clusterephemeralreports"), name)
}

err = deleteValidation(ctx, cephr)
if err != nil {
klog.ErrorS(err, "invalid resource", "name", name)
return nil, false, errors.NewBadRequest(fmt.Sprintf("invalid resource: %s", err.Error()))
}

if !isDryRun {
if err = c.deleteCephr(cephr); err != nil {
klog.ErrorS(err, "failed to delete cephr", "name", name)
return nil, false, errors.NewBadRequest(fmt.Sprintf("failed to delete clusterephemeralreport: %s", err.Error()))
}
if err := c.broadcaster.Action(watch.Deleted, cephr); err != nil {
klog.ErrorS(err, "failed to broadcast event")
}
}

return cephr, true, nil // TODO: Add protobuf
}

func (c *cephrStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *metainternalversion.ListOptions) (runtime.Object, error) {
isDryRun := slices.Contains(options.DryRun, "All")

obj, err := c.List(ctx, listOptions)
if err != nil {
klog.ErrorS(err, "Failed to find cephrs")
return nil, errors.NewBadRequest("Failed to find cluster ephemeral reports")
}

cephrList, ok := obj.(*reportsv1.ClusterEphemeralReportList)
if !ok {
klog.ErrorS(err, "Failed to parse cephrs")
return nil, errors.NewBadRequest("Failed to parse cluster ephemeral reports")
}

if !isDryRun {
for _, cephr := range cephrList.Items {
obj, isDeleted, err := c.Delete(ctx, cephr.GetName(), deleteValidation, options)
if !isDeleted {
klog.ErrorS(err, "Failed to delete cephr", "name", cephr.GetName())
return nil, errors.NewBadRequest(fmt.Sprintf("Failed to delete cluster ephemral report: %s", cephr.GetName()))
}
if err := c.broadcaster.Action(watch.Deleted, obj); err != nil {
klog.ErrorS(err, "failed to broadcast event")
}
}
}
return cephrList, nil
}

func (c *cephrStore) Watch(ctx context.Context, _ *metainternalversion.ListOptions) (watch.Interface, error) {
return c.broadcaster.Watch()
}

func (c *cephrStore) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1beta1.Table, error) {
var table metav1beta1.Table

switch t := object.(type) {
case *reportsv1.ClusterEphemeralReport:
table.ResourceVersion = t.ResourceVersion
table.SelfLink = t.SelfLink //nolint:staticcheck // keep deprecated field to be backward compatible
addClusterEphemeralReportToTable(&table, *t)
case *reportsv1.ClusterEphemeralReportList:
table.ResourceVersion = t.ResourceVersion
table.SelfLink = t.SelfLink //nolint:staticcheck // keep deprecated field to be backward compatible
table.Continue = t.Continue
addClusterEphemeralReportToTable(&table, t.Items...)
default:
}

return &table, nil
}

func (c *cephrStore) NamespaceScoped() bool {
return false
}

func (c *cephrStore) GetSingularName() string {
return "clusterephemeralreport"
}

func (c *cephrStore) ShortNames() []string {
return []string{"cephr"}
}

func (c *cephrStore) getCephr(name string) (*reportsv1.ClusterEphemeralReport, error) {
val, err := c.store.ClusterEphemeralReports().Get(context.TODO(), name)
if err != nil {
return nil, errorpkg.Wrapf(err, "could not find cluster ephemeral report in store")
}

return val.DeepCopy(), nil
}

func (c *cephrStore) listCephr() (*reportsv1.ClusterEphemeralReportList, error) {
valList, err := c.store.ClusterEphemeralReports().List(context.TODO())
if err != nil {
return nil, errorpkg.Wrapf(err, "could not find cluster ephemeral report in store")
}

reportList := &reportsv1.ClusterEphemeralReportList{
Items: valList,
}

klog.Infof("value found of length:%d", len(reportList.Items))
return reportList, nil
}

func (c *cephrStore) createCephr(report *reportsv1.ClusterEphemeralReport) error {
report.ResourceVersion = fmt.Sprint(1)
report.UID = uuid.NewUUID()
report.CreationTimestamp = metav1.Now()

return c.store.ClusterEphemeralReports().Create(context.TODO(), *report)
}

func (c *cephrStore) updateCephr(report *reportsv1.ClusterEphemeralReport, force bool) error {
if !force {
oldReport, err := c.getCephr(report.GetName())
if err != nil {
return errorpkg.Wrapf(err, "old cluster ephemeral report not found")
}
oldRV, err := strconv.ParseInt(oldReport.ResourceVersion, 10, 64)
if err != nil {
return errorpkg.Wrapf(err, "could not parse resource version")
}

report.ResourceVersion = fmt.Sprint(oldRV + 1)
} else {
report.ResourceVersion = "1"
}

return c.store.ClusterEphemeralReports().Update(context.TODO(), *report)
}

func (c *cephrStore) deleteCephr(report *reportsv1.ClusterEphemeralReport) error {
return c.store.ClusterEphemeralReports().Delete(context.TODO(), report.Name)
}
Loading

0 comments on commit cf8e246

Please sign in to comment.