mirror of
https://github.com/projectcapsule/capsule.git
synced 2026-02-14 09:59:57 +00:00
* fix(controller): decode old object for delete requests Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: modernize golang Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(config): remove usergroups default Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * fix(config): remove usergroups default Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * sec(ghsa-2ww6-hf35-mfjm): intercept namespace subresource Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * chore: conflicts Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> * feat(api): add rulestatus api Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com> --------- Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
223 lines
5.6 KiB
Go
223 lines
5.6 KiB
Go
// Copyright 2020-2026 Project Capsule Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
package template
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
k8smeta "k8s.io/apimachinery/pkg/api/meta"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"sigs.k8s.io/controller-runtime/pkg/client"
|
|
|
|
"github.com/projectcapsule/capsule/pkg/api/meta"
|
|
"github.com/projectcapsule/capsule/pkg/runtime/selectors"
|
|
)
|
|
|
|
// Reference
|
|
// +kubebuilder:object:generate=true
|
|
type ResourceReference struct {
|
|
// Kind of the referent.
|
|
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
|
|
Kind string `json:"kind" protobuf:"bytes,1,opt,name=kind"`
|
|
// API version of the referent.
|
|
APIVersion string `json:"apiVersion" protobuf:"bytes,5,opt,name=apiVersion"`
|
|
// Name of the values referent. This is useful
|
|
// when you traying to get a specific resource
|
|
// +kubebuilder:validation:MinLength=1
|
|
// +kubebuilder:validation:MaxLength=253
|
|
// +optional
|
|
Name string `json:"name,omitempty"`
|
|
// Namespace of the values referent.
|
|
// +optional
|
|
Namespace meta.RFC1123SubdomainName `json:"namespace,omitempty"`
|
|
// Selector which allows to get any amount of these resources based on labels
|
|
// +optional
|
|
Selector *metav1.LabelSelector `json:"selector,omitempty"`
|
|
// Only relevant if name is set. If an item is not optional, there will be an error thrown when it does not exist
|
|
// +kubebuilder:default:=true
|
|
Optional bool `json:"optional,omitempty"`
|
|
}
|
|
|
|
func (t ResourceReference) RequiresTemplating() bool {
|
|
if RequiresFastTemplate(t.Name) {
|
|
return true
|
|
}
|
|
|
|
if RequiresFastTemplate(string(t.Namespace)) {
|
|
return true
|
|
}
|
|
|
|
if SelectorRequiresTemplating(t.Selector) {
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (t ResourceReference) LoadTemplated(templateContext map[string]string) (ResourceReference, error) {
|
|
if !t.RequiresTemplating() || templateContext == nil {
|
|
return t, nil
|
|
}
|
|
|
|
out := t
|
|
|
|
// Name + Namespace
|
|
if out.Name != "" {
|
|
out.Name = FastTemplate(out.Name, templateContext)
|
|
}
|
|
|
|
if out.Namespace != "" {
|
|
out.Namespace = meta.RFC1123SubdomainName(
|
|
FastTemplate(string(out.Namespace), templateContext),
|
|
)
|
|
}
|
|
|
|
// Selector
|
|
if out.Selector != nil {
|
|
selCopy, err := FastTemplateLabelSelector(out.Selector, templateContext)
|
|
if err != nil {
|
|
return ResourceReference{}, err
|
|
}
|
|
|
|
out.Selector = selCopy
|
|
}
|
|
|
|
return out, nil
|
|
}
|
|
|
|
func (t ResourceReference) LoadResources(
|
|
ctx context.Context,
|
|
kubeClient client.Client,
|
|
restMapper k8smeta.RESTMapper,
|
|
namespace string,
|
|
additionSelectors []labels.Selector,
|
|
templateContext map[string]string,
|
|
allowClusterScoped bool,
|
|
) ([]*unstructured.Unstructured, error) {
|
|
isNamespaced, err := t.IsNamespacedGVK(restMapper)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !allowClusterScoped && !isNamespaced {
|
|
return nil, fmt.Errorf("cluster-scoped kind %s/%s is not allowed", t.APIVersion, t.Kind)
|
|
}
|
|
|
|
ref, err := t.LoadTemplated(templateContext)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return ref.loadResources(ctx, kubeClient, restMapper, namespace, additionSelectors)
|
|
}
|
|
|
|
func (t ResourceReference) IsNamespacedGVK(
|
|
restMapper k8smeta.RESTMapper,
|
|
) (bool, error) {
|
|
gv, err := schema.ParseGroupVersion(t.APIVersion)
|
|
if err != nil {
|
|
return false, fmt.Errorf("invalid apiVersion %q: %w", t.APIVersion, err)
|
|
}
|
|
|
|
gvk := gv.WithKind(t.Kind)
|
|
|
|
mapping, err := restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to resolve GVK %s: %w", gvk.String(), err)
|
|
}
|
|
|
|
isNamespaced := mapping.Scope.Name() == k8smeta.RESTScopeNameNamespace
|
|
|
|
return isNamespaced, nil
|
|
}
|
|
|
|
func (t ResourceReference) loadResources(
|
|
ctx context.Context,
|
|
kubeClient client.Client,
|
|
restMapper k8smeta.RESTMapper,
|
|
namespace string,
|
|
additionSelectors []labels.Selector,
|
|
) ([]*unstructured.Unstructured, error) {
|
|
ns := t.Namespace
|
|
|
|
if namespace != "" {
|
|
ns = meta.RFC1123SubdomainName(namespace)
|
|
}
|
|
|
|
// GET path (single object)
|
|
if t.Name != "" {
|
|
obj := &unstructured.Unstructured{}
|
|
obj.SetAPIVersion(t.APIVersion)
|
|
obj.SetKind(t.Kind)
|
|
|
|
key := client.ObjectKey{
|
|
Name: t.Name,
|
|
Namespace: string(ns),
|
|
}
|
|
|
|
if err := kubeClient.Get(ctx, key, obj); err != nil {
|
|
if apierrors.IsNotFound(err) && t.Optional {
|
|
return nil, nil
|
|
}
|
|
|
|
return nil, fmt.Errorf("failed to get %s/%s: %w", t.Kind, t.Name, err)
|
|
}
|
|
|
|
return []*unstructured.Unstructured{obj}, nil
|
|
}
|
|
|
|
list := &unstructured.UnstructuredList{}
|
|
list.SetAPIVersion(t.APIVersion)
|
|
list.SetKind(t.Kind + "List")
|
|
|
|
var opts []client.ListOption
|
|
if ns != "" {
|
|
opts = append(opts, client.InNamespace(string(ns)))
|
|
}
|
|
|
|
// Convert t.Selector (metav1) to labels.Selector if present
|
|
var tenantSel labels.Selector
|
|
|
|
if t.Selector != nil {
|
|
s, err := metav1.LabelSelectorAsSelector(t.Selector)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("invalid label selector: %w", err)
|
|
}
|
|
|
|
tenantSel = s
|
|
}
|
|
|
|
all := make([]labels.Selector, 0, len(additionSelectors)+1)
|
|
|
|
for _, s := range additionSelectors {
|
|
if s != nil {
|
|
all = append(all, s)
|
|
}
|
|
}
|
|
|
|
if tenantSel != nil {
|
|
all = append(all, tenantSel)
|
|
}
|
|
|
|
if len(all) > 0 {
|
|
combined := selectors.CombineSelectors(all...)
|
|
opts = append(opts, client.MatchingLabelsSelector{Selector: combined})
|
|
}
|
|
|
|
if err := kubeClient.List(ctx, list, opts...); err != nil {
|
|
return nil, fmt.Errorf("failed to list %s: %w", t.Kind, err)
|
|
}
|
|
|
|
results := make([]*unstructured.Unstructured, 0, len(list.Items))
|
|
for i := range list.Items {
|
|
results = append(results, list.Items[i].DeepCopy())
|
|
}
|
|
|
|
return results, nil
|
|
}
|