Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add namespace scoped option #462

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions api/v1/cosmosfullnode_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,18 @@ type FullNodeSpec struct {
// complexity of the CosmosFullNodeController.
// +optional
SelfHeal *SelfHealSpec `json:"selfHeal"`

// NamespaceSelector allows filtering which namespaces the operator should watch
// +optional
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`

// AllowNamespaces is a list of namespaces to explicitly include for management
// +optional
AllowNamespaces []string `json:"allowNamespaces,omitempty"`

// DenyNamespaces is a list of namespaces to explicitly exclude from management
// +optional
DenyNamespaces []string `json:"denyNamespaces,omitempty"`
}

type FullNodeType string
Expand Down
16 changes: 16 additions & 0 deletions api/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

57 changes: 57 additions & 0 deletions config/crd/bases/cosmos.strange.love_cosmosfullnodes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ spec:
spec:
description: FullNodeSpec defines the desired state of CosmosFullNode
properties:
allowNamespaces:
description: AllowNamespaces is a list of namespaces to explicitly
include for management
items:
type: string
type: array
chain:
description: Blockchain-specific configuration.
properties:
Expand Down Expand Up @@ -386,6 +392,12 @@ spec:
- chainID
- network
type: object
denyNamespaces:
description: DenyNamespaces is a list of namespaces to explicitly
exclude from management
items:
type: string
type: array
instanceOverrides:
additionalProperties:
description: InstanceOverridesSpec allows overriding an instance
Expand Down Expand Up @@ -552,6 +564,51 @@ spec:
Example: cosmos-1
Used for debugging.
type: object
namespaceSelector:
description: NamespaceSelector allows filtering which namespaces the
operator should watch
properties:
matchExpressions:
description: matchExpressions is a list of label selector requirements.
The requirements are ANDed.
items:
description: |-
A label selector requirement is a selector that contains values, a key, and an operator that
relates the key and values.
properties:
key:
description: key is the label key that the selector applies
to.
type: string
operator:
description: |-
operator represents a key's relationship to a set of values.
Valid operators are In, NotIn, Exists and DoesNotExist.
type: string
values:
description: |-
values is an array of string values. If the operator is In or NotIn,
the values array must be non-empty. If the operator is Exists or DoesNotExist,
the values array must be empty. This array is replaced during a strategic
merge patch.
items:
type: string
type: array
required:
- key
- operator
type: object
type: array
matchLabels:
additionalProperties:
type: string
description: |-
matchLabels is a map of {key,value} pairs. A single {key,value} in the matchLabels
map is equivalent to an element of matchExpressions, whose key field is "key", the
operator is "In", and the values array contains only "value". The requirements are ANDed.
type: object
type: object
x-kubernetes-map-type: atomic
ordinals:
description: |-
Ordinals controls the numbering of replica indices in a CosmosFullnode spec.
Expand Down
44 changes: 44 additions & 0 deletions controllers/cosmosfullnode_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ import (
"github.com/strangelove-ventures/cosmos-operator/internal/fullnode"
"github.com/strangelove-ventures/cosmos-operator/internal/kube"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand Down Expand Up @@ -55,6 +58,9 @@ type CosmosFullNodeReconciler struct {
serviceAccountControl fullnode.ServiceAccountControl
clusterRoleControl fullnode.RoleControl
clusterRoleBindingControl fullnode.RoleBindingControl
NamespaceSelector *metav1.LabelSelector
AllowNamespaces []string
DenyNamespaces []string
}

// NewFullNode returns a valid CosmosFullNode controller.
Expand Down Expand Up @@ -115,6 +121,12 @@ func (r *CosmosFullNodeReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return stopResult, client.IgnoreNotFound(err)
}

//Namespace filtering logic
if !r.isNamespaceAllowed(crd.Spec, req.Namespace) {
logger.V(1).Info("Skipping reconciliation for namespace", "namespace", req.Namespace)
return ctrl.Result{}, nil
}

reporter := kube.NewEventReporter(logger, r.recorder, crd)

fullnode.ResetStatus(crd)
Expand Down Expand Up @@ -209,6 +221,38 @@ func (r *CosmosFullNodeReconciler) Reconcile(ctx context.Context, req ctrl.Reque
return ctrl.Result{RequeueAfter: 60 * time.Second}, nil
}

func (r *CosmosFullNodeReconciler) isNamespaceAllowed(spec cosmosv1.FullNodeSpec, namespace string) bool {
// Check DenyNamespaces
for _, ns := range spec.DenyNamespaces {
if ns == namespace {
return false
}
}

// Check AllowNamespaces
if len(spec.AllowNamespaces) > 0 {
for _, ns := range spec.AllowNamespaces {
if ns == namespace {
return true
}
}
return false
}

// Check NamespaceSelector
if spec.NamespaceSelector != nil {
// Implement label selector logic here
// This would require fetching the Namespace object and checking its labels
ns := &corev1.Namespace{}
if err := r.Get(context.TODO(), types.NamespacedName{Name: namespace}, ns); err != nil {
return false
}
return labels.SelectorFromSet(spec.NamespaceSelector.MatchLabels).Matches(labels.Set(ns.Labels))
}

return true
}

func (r *CosmosFullNodeReconciler) resultWithErr(crd *cosmosv1.CosmosFullNode, err kube.ReconcileError) (ctrl.Result, kube.ReconcileError) {
if err.IsTransient() {
r.recorder.Event(crd, kube.EventWarning, "ErrorTransient", fmt.Sprintf("%v; retrying.", err))
Expand Down
6 changes: 6 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ var (
profileMode string
logLevel string
logFormat string
namespaceRegex string
)

func rootCmd() *cobra.Command {
Expand All @@ -101,6 +102,7 @@ func rootCmd() *cobra.Command {
root.Flags().StringVar(&profileMode, "profile", "", "Enable profiling and save profile to working dir. (Must be one of 'cpu', or 'mem'.)")
root.Flags().StringVar(&logLevel, "log-level", "info", "Logging level one of 'error', 'info', 'debug'")
root.Flags().StringVar(&logFormat, "log-format", "console", "Logging format one of 'console' or 'json'")
root.Flags().StringVar(&namespaceRegex, "namespace", "*", "Regex pattern for namespaces to watch")

if err := viper.BindPFlags(root.Flags()); err != nil {
panic(err)
Expand Down Expand Up @@ -160,6 +162,10 @@ func startManager(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unable to start manager: %w", err)
}

fmt.Println("Manager started")
//Example Print.
fmt.Println("Port: ", mgr.GetWebhookServer().Port)

ctx := cmd.Context()

// CacheController which fetches CometBFT status in the background.
Expand Down
31 changes: 31 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import (
"regexp"
"testing"
)

func TestNamespaceRegex(t *testing.T) {
regex := regexp.MustCompile(`^cosmos-(fullnode|sentry)-[a-zA-Z0-9]+-(?:mainnet|testnet)$`)

testCases := []struct {
name string
namespace string
expected bool
}{
{"Valid fullnode mainnet", "cosmos-fullnode-osmosis-mainnet", true},
{"Valid sentry testnet", "cosmos-sentry-axelar-testnet", true},
{"Invalid prefix", "invalid-fullnode-kava-mainnet", false},
{"Invalid suffix", "cosmos-fullnode-sei-devnet", false},
{"Valid with numbers", "cosmos-fullnode-chain123-mainnet", true},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
result := regex.MatchString(tc.namespace)
if result != tc.expected {
t.Errorf("Expected %v, got %v for namespace %s", tc.expected, result, tc.namespace)
}
})
}
}
Loading