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

Implement support for cluster level resources #42

Merged
merged 3 commits into from
Jan 9, 2021
Merged
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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@

All notable changes to this project will be documented in this file.

## Unreleased
## 0.9.0 - 2020-01-09

* 41: Introduce support for cluster-wide resources
* 36: Update last-applied-configuration annotation when patching a resource
* 18: Implement /v1/dryrun API

Expand Down
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ Use KubeMod to:
* [Modification of metadata](#modification-of-metadata)
* [Sidecar injection](#sidecar-injection)
* [Resource rejection](#resource-rejection)
* [Understanding ModRules](#understanding-modrules)
* [Anatomy of a ModRule](#anatomy-of-a-modrule)
* [Match section](#match-section)
* [Patch section](#patch-section)
* [Miscellaneous](#miscellaneous)
* [Namespaced and cluster-wide resources](#namespaced-and-cluster-wide-resources)
* [Note on idempotency](#note-on-idempotency)
* [Debugging ModRules](#debugging-modrules)
* [Declarative kubectl apply](#declarative-kubectl-apply)
Expand Down Expand Up @@ -343,9 +344,9 @@ spec:

---

## Understanding ModRules
## Anatomy of a ModRule

A `ModRule` has a `type`, a `match` section, and a `patch` section.
A `ModRule` consists of a `type`, a `match` section, and a `patch` section.

```yaml
apiVersion: api.kubemod.io/v1beta1
Expand Down Expand Up @@ -732,6 +733,14 @@ The field is a Golang template evaluated in the context of the object being reje

## Miscellaneous

### Namespaced and cluster-wide resources

KubeMod can patch/reject both namespaced and cluster-wide resources.

If a ModRule is deployed to any namespace other than `kubemod-system`, the ModRule applies only to objects deployed/updated in that same namespace.

ModRules deployed to namespace `kubemod-system` apply to cluster-wide resources such as `Namespace` or `ClusterRole`.

### Note on idempotency

Make sure your patch ModRules are idempotent — executing them multiple times against the same object should lead to no changes beyond the first execution.
Expand Down
4 changes: 2 additions & 2 deletions app/operatorapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ func NewKubeModOperatorApp(
}

// NewControllerManager instantiates a new controller manager.
func NewControllerManager(scheme *runtime.Scheme, metricsAddr string, enableLeaderElection EnableLeaderElection, log logr.Logger) (manager.Manager, error) {
func NewControllerManager(scheme *runtime.Scheme, metricsAddr OperatorMetricsAddr, enableLeaderElection EnableLeaderElection, log logr.Logger) (manager.Manager, error) {
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
MetricsBindAddress: string(metricsAddr),
Port: 9443,
LeaderElection: bool(enableLeaderElection),
LeaderElectionID: "f950e141.kubemod.io",
Expand Down
4 changes: 3 additions & 1 deletion app/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ import (

type EnableLeaderElection bool
type EnableDevModeLog bool
type OperatorMetricsAddr string

func InitializeKubeModOperatorApp(
scheme *runtime.Scheme,
metricsAddr string,
metricsAddr OperatorMetricsAddr,
clusterModRulesNamespace controllers.ClusterModRulesNamespace,
enableLeaderElection EnableLeaderElection,
log logr.Logger) (*KubeModOperatorApp, error) {
wire.Build(
Expand Down
6 changes: 4 additions & 2 deletions app/wire_gen.go

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

37 changes: 26 additions & 11 deletions controllers/modrule_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,22 +28,27 @@ import (
"github.com/kubemod/kubemod/core"
)

// ClusterModRulesNamespace is a type of string used by DI to inject the namespace where cluster-wide ModRules are deployed.
type ClusterModRulesNamespace string

// ModRuleReconciler reconciles a ModRule object
type ModRuleReconciler struct {
client client.Client
log logr.Logger
scheme *runtime.Scheme
modRuleStore *core.ModRuleStore
client client.Client
log logr.Logger
scheme *runtime.Scheme
modRuleStore *core.ModRuleStore
clusterModRulesNamespace string
}

// NewModRuleReconciler creates a new ModRuleReconciler.
func NewModRuleReconciler(manager manager.Manager, modRuleStore *core.ModRuleStore, log logr.Logger) (*ModRuleReconciler, error) {
func NewModRuleReconciler(manager manager.Manager, modRuleStore *core.ModRuleStore, clusterModRulesNamespace ClusterModRulesNamespace, log logr.Logger) (*ModRuleReconciler, error) {

reconciler := &ModRuleReconciler{
client: manager.GetClient(),
log: log.WithName("controllers").WithName("modrule"),
scheme: manager.GetScheme(),
modRuleStore: modRuleStore,
client: manager.GetClient(),
log: log.WithName("controllers").WithName("modrule"),
scheme: manager.GetScheme(),
modRuleStore: modRuleStore,
clusterModRulesNamespace: string(clusterModRulesNamespace),
}

return reconciler, nil
Expand All @@ -58,12 +63,20 @@ func (r *ModRuleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
log := r.log.WithValues("modrule", req.NamespacedName)

storeNamespace := req.Namespace

// If the ModRule is stored in the cluster-wide namespace, store the ModRule under an empty namespace
// in order to match non-namespaced resources.
if storeNamespace == r.clusterModRulesNamespace {
storeNamespace = ""
}

if err := r.client.Get(ctx, req.NamespacedName, &modRule); err != nil {

// If the modrule is not found, then it has been deleted.
if apierrors.IsNotFound(err) {
// Delete the ModRule from the ModRule memory store.
r.modRuleStore.Delete(req.Namespace, req.Name)
r.modRuleStore.Delete(storeNamespace, req.Name)
} else {
log.Error(err, "unable to fetch ModRule")
}
Expand All @@ -73,7 +86,9 @@ func (r *ModRuleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
return ctrl.Result{}, client.IgnoreNotFound((err))
}

// Store the new ModRule in our memory store.
// Store the new ModRule in our memory store, but before we do, clear the namespace
// in case the ModRule is deployed to the cluster-wide namespace.
modRule.Namespace = storeNamespace
r.modRuleStore.Put(&modRule)

log.V(1).Info("Successfully stored ModRule")
Expand Down
12 changes: 10 additions & 2 deletions core/dragnetwebhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,16 @@ func NewDragnetWebhookHandler(manager manager.Manager, modRuleStore *ModRuleStor
func (h *DragnetWebhookHandler) Handle(ctx context.Context, req admission.Request) admission.Response {
log := h.log.WithValues("request uid", req.UID, "namespace", req.Namespace, "resource", fmt.Sprintf("%v/%v", req.Resource.Resource, req.Name), "operation", req.Operation)

storeNamespace := req.Namespace
// If the target object is a namespace, UPDATE operations will pass in the namespace itself as the owner of the namespace.
// This is misleading - namespaces are cluster-wide objects.
// Set the storeNamespace to an empty string to reflect that and pick up the cluster-wide modrules.
if req.Kind.Group == "" && req.Kind.Version == "v1" && req.Kind.Kind == "Namespace" {
storeNamespace = ""
}

// First run patch operations.
patchedJSON, patch, err := h.modRuleStore.CalculatePatch(req.Namespace, req.Object.Raw, log)
patchedJSON, patch, err := h.modRuleStore.CalculatePatch(storeNamespace, req.Object.Raw, log)

if err != nil {
log.Error(err, "Failed to calculate patch")
Expand All @@ -58,7 +66,7 @@ func (h *DragnetWebhookHandler) Handle(ctx context.Context, req admission.Reques
}

// Then test the result against the set of relevant Reject rules.
rejections := h.modRuleStore.DetermineRejections(req.Namespace, patchedJSON, log)
rejections := h.modRuleStore.DetermineRejections(storeNamespace, patchedJSON, log)

if len(rejections) > 0 {
rejectionMessages := strings.Join(rejections, ",")
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ k8s.io/apiextensions-apiserver v0.18.6 h1:vDlk7cyFsDyfwn2rNAO2DbmUbvXy5yT5GE3rrq
k8s.io/apiextensions-apiserver v0.18.6/go.mod h1:lv89S7fUysXjLZO7ke783xOwVTm6lKizADfvUM/SS/M=
k8s.io/apimachinery v0.18.6 h1:RtFHnfGNfd1N0LeSrKCUznz5xtUP1elRGvHJbL3Ntag=
k8s.io/apimachinery v0.18.6/go.mod h1:OaXp26zu/5J7p0f92ASynJa1pZo06YlV9fG7BoWbCko=
k8s.io/apimachinery v0.20.1 h1:LAhz8pKbgR8tUwn7boK+b2HZdt7MiTu2mkYtFMUjTRQ=
k8s.io/apiserver v0.18.6/go.mod h1:Zt2XvTHuaZjBz6EFYzpp+X4hTmgWGy8AthNVnTdm3Wg=
k8s.io/client-go v0.18.6 h1:I+oWqJbibLSGsZj8Xs8F0aWVXJVIoUHWaaJV3kUN/Zw=
k8s.io/client-go v0.18.6/go.mod h1:/fwtGLjYMS1MaM5oi+eXhKwG+1UHidUEXRh6cNsdO0Q=
Expand Down
10 changes: 7 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

apiv1beta1 "github.com/kubemod/kubemod/api/v1beta1"
"github.com/kubemod/kubemod/app"
"github.com/kubemod/kubemod/controllers"
// +kubebuilder:scaffold:imports
)

Expand All @@ -46,8 +47,9 @@ type Config struct {
RunOperator bool
RunWebApp bool

WebAppAddr string
OperatorMetricsAddr string
WebAppAddr string
OperatorMetricsAddr string
ClusterModRulesNamespace string

EnableLeaderElection bool
EnableDevModeLog bool
Expand All @@ -62,6 +64,7 @@ func main() {

flag.StringVar(&config.WebAppAddr, "webapp-addr", ":8081", "The address the web app binds to.")
flag.StringVar(&config.OperatorMetricsAddr, "operator-metrics-addr", ":8082", "The address the operator metric endpoint binds to.")
flag.StringVar(&config.ClusterModRulesNamespace, "cluster-modrules-namespace", "kubemod-system", "The namespace where cluster-wide ModRules are deployed.")
flag.BoolVar(&config.EnableLeaderElection, "enable-leader-election", false,
"Enable leader election for KubeMod operator. "+
"Enabling this will ensure there is only one active controller manager.")
Expand Down Expand Up @@ -98,7 +101,8 @@ func run(config *Config) error {
if config.RunOperator {
_, err := app.InitializeKubeModOperatorApp(
scheme,
config.OperatorMetricsAddr,
app.OperatorMetricsAddr(config.OperatorMetricsAddr),
controllers.ClusterModRulesNamespace(config.ClusterModRulesNamespace),
app.EnableLeaderElection(config.EnableLeaderElection),
log)

Expand Down
19 changes: 19 additions & 0 deletions samples/modrules/modrule-4.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: api.kubemod.io/v1beta1
kind: ModRule
metadata:
name: patch-istio-namespace
namespace: kubemod-system
spec:
type: Patch

match:
- select: '$.kind'
matchValue: 'Namespace'

- select: '$.metadata.name'
matchValue: 'my-namespace'

patch:
- op: add
path: /metadata/labels/istio.io~1rev
value: canary
19 changes: 19 additions & 0 deletions samples/modrules/modrule-5.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
apiVersion: api.kubemod.io/v1beta1
kind: ModRule
metadata:
name: patch-clusterrole
namespace: kubemod-system
spec:
type: Patch

match:
- select: '$.kind'
matchValue: 'ClusterRole'

- select: '$.metadata.name'
matchValue: 'my-clusterrole'

patch:
- op: add
path: /metadata/labels/color
value: blue
4 changes: 4 additions & 0 deletions samples/stack/my-clusterrole.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: my-clusterrole
6 changes: 6 additions & 0 deletions samples/stack/my-namespace.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apiVersion: v1
kind: Namespace
metadata:
name: my-namespace
labels:
color: blue