Skip to content

Commit

Permalink
Introduce the controllers.namespaceLabels value
Browse files Browse the repository at this point in the history
- This lets the operator set custom labels on korifi space namespaces
- For instance disabling istio sidecar injection or changing the
  pod-security-admission levels

Co-authored-by: Kieron Browne <kbrowne@vmware.com>
Co-authored-by: Georgi Sabev <georgethebeatle@gmail.com>
  • Loading branch information
Kieron Browne and georgethebeatle committed Jan 9, 2023
1 parent 4e1f92f commit 148d1ee
Show file tree
Hide file tree
Showing 15 changed files with 213 additions and 31 deletions.
1 change: 1 addition & 0 deletions README.helm.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Here are all the values that can be set for the chart:
- `controllers`:
- `image` (_String_): Reference to the controllers container image.
- `include` (_Boolean_): Deploy the controllers component.
- `namespaceLabels`: Key value pairs that are going to be set as labels in the workload namespaces created by Korifi
- `processDefaults`:
- `diskQuotaMB` (_Integer_): Default disk quota for the `web` process.
- `memoryMB` (_Integer_): Default memory limit for the `web` process.
Expand Down
1 change: 1 addition & 0 deletions controllers/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type ControllerConfig struct {
WorkloadsTLSSecretNamespace string `yaml:"workloads_tls_secret_namespace"`
BuilderName string `yaml:"builderName"`
RunnerName string `yaml:"runnerName"`
NamespaceLabels map[string]string `yaml:"namespaceLabels"`
}

type CFProcessDefaults struct {
Expand Down
1 change: 1 addition & 0 deletions controllers/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ var _ = Describe("LoadFromPath", func() {
WorkloadsTLSSecretNamespace: "workloadsTLSSecretNamespace",
BuilderName: "buildReconciler",
RunnerName: "statefulset-runner",
NamespaceLabels: map[string]string{},
}))
})

Expand Down
29 changes: 19 additions & 10 deletions controllers/controllers/workloads/cforg_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/source"

korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
"code.cloudfoundry.org/korifi/controllers/controllers/workloads/labels"
"code.cloudfoundry.org/korifi/tools/k8s"
)

Expand All @@ -44,17 +45,23 @@ type CFOrgReconciler struct {
scheme *runtime.Scheme
log logr.Logger
containerRegistrySecretName string
labelCompiler labels.Compiler
}

func NewCFOrgReconciler(client client.Client, scheme *runtime.Scheme, log logr.Logger, containerRegistrySecretName string) *k8s.PatchingReconciler[korifiv1alpha1.CFOrg, *korifiv1alpha1.CFOrg] {
orgReconciler := CFOrgReconciler{
func NewCFOrgReconciler(
client client.Client,
scheme *runtime.Scheme,
log logr.Logger,
containerRegistrySecretName string,
labelCompiler labels.Compiler,
) *k8s.PatchingReconciler[korifiv1alpha1.CFOrg, *korifiv1alpha1.CFOrg] {
return k8s.NewPatchingReconciler[korifiv1alpha1.CFOrg, *korifiv1alpha1.CFOrg](log, client, &CFOrgReconciler{
client: client,
scheme: scheme,
log: log,
containerRegistrySecretName: containerRegistrySecretName,
}

return k8s.NewPatchingReconciler[korifiv1alpha1.CFOrg, *korifiv1alpha1.CFOrg](log, client, &orgReconciler)
labelCompiler: labelCompiler,
})
}

const (
Expand Down Expand Up @@ -106,21 +113,24 @@ func (r *CFOrgReconciler) ReconcileResource(ctx context.Context, cfOrg *korifiv1
return ctrl.Result{}, err
}

cfOrg.Status.GUID = cfOrg.Name

getConditionOrSetAsUnknown(&cfOrg.Status.Conditions, korifiv1alpha1.ReadyConditionType)

if !cfOrg.GetDeletionTimestamp().IsZero() {
return r.finalize(ctx, log, cfOrg)
}

labels := map[string]string{korifiv1alpha1.OrgNameLabel: cfOrg.Spec.DisplayName}
err := createOrPatchNamespace(ctx, r.client, log, cfOrg, labels)
err := createOrPatchNamespace(ctx, r.client, log, cfOrg, r.labelCompiler.Compile(map[string]string{
korifiv1alpha1.OrgNameLabel: cfOrg.Spec.DisplayName,
}))
if err != nil {
log.Error(err, "Error creating namespace")
return ctrl.Result{}, err
}

namespace, ok := getNamespace(ctx, log, r.client, cfOrg.Name)
if !ok {
err = getNamespace(ctx, log, r.client, cfOrg.Name)
if err != nil {
return ctrl.Result{RequeueAfter: 100 * time.Millisecond}, nil
}

Expand All @@ -136,7 +146,6 @@ func (r *CFOrgReconciler) ReconcileResource(ctx context.Context, cfOrg *korifiv1
return ctrl.Result{}, err
}

cfOrg.Status.GUID = namespace.Name
meta.SetStatusCondition(&cfOrg.Status.Conditions, metav1.Condition{
Type: StatusConditionReady,
Status: metav1.ConditionTrue,
Expand Down
25 changes: 15 additions & 10 deletions controllers/controllers/workloads/cfspace_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ import (
"time"

korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
"code.cloudfoundry.org/korifi/controllers/controllers/workloads/labels"
"code.cloudfoundry.org/korifi/tools/k8s"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
k8s_labels "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
Expand All @@ -52,6 +53,7 @@ type CFSpaceReconciler struct {
log logr.Logger
containerRegistrySecretName string
rootNamespace string
labelCompiler labels.Compiler
}

func NewCFSpaceReconciler(
Expand All @@ -60,15 +62,16 @@ func NewCFSpaceReconciler(
log logr.Logger,
containerRegistrySecretName string,
rootNamespace string,
labelCompiler labels.Compiler,
) *k8s.PatchingReconciler[korifiv1alpha1.CFSpace, *korifiv1alpha1.CFSpace] {
spaceReconciler := CFSpaceReconciler{
return k8s.NewPatchingReconciler[korifiv1alpha1.CFSpace, *korifiv1alpha1.CFSpace](log, client, &CFSpaceReconciler{
client: client,
scheme: scheme,
log: log,
containerRegistrySecretName: containerRegistrySecretName,
rootNamespace: rootNamespace,
}
return k8s.NewPatchingReconciler[korifiv1alpha1.CFSpace, *korifiv1alpha1.CFSpace](log, client, &spaceReconciler)
labelCompiler: labelCompiler,
})
}

//+kubebuilder:rbac:groups=korifi.cloudfoundry.org,resources=cfspaces,verbs=get;list;watch;create;update;patch;delete
Expand Down Expand Up @@ -97,21 +100,24 @@ func (r *CFSpaceReconciler) ReconcileResource(ctx context.Context, cfSpace *kori
return ctrl.Result{}, err
}

cfSpace.Status.GUID = cfSpace.GetName()

getConditionOrSetAsUnknown(&cfSpace.Status.Conditions, korifiv1alpha1.ReadyConditionType)

if !cfSpace.GetDeletionTimestamp().IsZero() {
return r.finalize(ctx, log, cfSpace)
}

labels := map[string]string{korifiv1alpha1.SpaceNameLabel: cfSpace.Spec.DisplayName}
err := createOrPatchNamespace(ctx, r.client, log, cfSpace, labels)
err := createOrPatchNamespace(ctx, r.client, log, cfSpace, r.labelCompiler.Compile(map[string]string{
korifiv1alpha1.SpaceNameLabel: cfSpace.Spec.DisplayName,
}))
if err != nil {
log.Error(err, "Error creating namespace")
return ctrl.Result{}, err
}

namespace, ok := getNamespace(ctx, log, r.client, cfSpace.Name)
if !ok {
err = getNamespace(ctx, log, r.client, cfSpace.Name)
if err != nil {
return ctrl.Result{RequeueAfter: 100 * time.Millisecond}, nil
}

Expand All @@ -133,7 +139,6 @@ func (r *CFSpaceReconciler) ReconcileResource(ctx context.Context, cfSpace *kori
return ctrl.Result{}, err
}

cfSpace.Status.GUID = namespace.Name
meta.SetStatusCondition(&cfSpace.Status.Conditions, metav1.Condition{
Type: StatusConditionReady,
Status: metav1.ConditionTrue,
Expand Down Expand Up @@ -220,7 +225,7 @@ func (r *CFSpaceReconciler) reconcileServiceAccounts(ctx context.Context, space
}

propagatedServiceAccounts := new(corev1.ServiceAccountList)
labelSelector, err := labels.ValidatedSelectorFromSet(map[string]string{
labelSelector, err := k8s_labels.ValidatedSelectorFromSet(map[string]string{
korifiv1alpha1.PropagatedFromLabel: r.rootNamespace,
})
if err != nil {
Expand Down
7 changes: 3 additions & 4 deletions controllers/controllers/workloads/cfspace_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"context"
"time"

korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
. "code.cloudfoundry.org/korifi/controllers/controllers/workloads/testutils"
"code.cloudfoundry.org/korifi/tools/k8s"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gstruct"
Expand All @@ -15,10 +18,6 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/pod-security-admission/api"
"sigs.k8s.io/controller-runtime/pkg/client"

korifiv1alpha1 "code.cloudfoundry.org/korifi/controllers/api/v1alpha1"
. "code.cloudfoundry.org/korifi/controllers/controllers/workloads/testutils"
"code.cloudfoundry.org/korifi/tools/k8s"
)

var _ = Describe("CFSpaceReconciler Integration Tests", func() {
Expand Down
44 changes: 44 additions & 0 deletions controllers/controllers/workloads/labels/compiler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package labels

// Compiler is a reusable map composer. It persists a set of defaults, which
// can be overridden when calling Compile() to produce the combined map.
type Compiler struct {
defaults map[string]string
}

func NewCompiler() Compiler {
return Compiler{
defaults: map[string]string{},
}
}

func (o Compiler) Defaults(defaults map[string]string) Compiler {
defaultsCopy := copyMap(o.defaults)
for k, v := range defaults {
defaultsCopy[k] = v
}
return Compiler{
defaults: defaultsCopy,
}
}

func (o Compiler) Compile(overrides map[string]string) map[string]string {
res := map[string]string{}
for k, v := range o.defaults {
res[k] = v
}
for k, v := range overrides {
res[k] = v
}

return res
}

func copyMap(src map[string]string) map[string]string {
dst := map[string]string{}
for k, v := range src {
dst[k] = v
}

return dst
}
13 changes: 13 additions & 0 deletions controllers/controllers/workloads/labels/compiler_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package labels_test

import (
"testing"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func TestLabels(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Labels Suite")
}
84 changes: 84 additions & 0 deletions controllers/controllers/workloads/labels/compiler_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package labels_test

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"code.cloudfoundry.org/korifi/controllers/controllers/workloads/labels"
)

var _ = Describe("Labels", func() {
var (
compiler labels.Compiler
override map[string]string
output map[string]string
)

BeforeEach(func() {
override = nil
compiler = labels.NewCompiler()
})

JustBeforeEach(func() {
output = compiler.Compile(override)
})

It("will return empty if no defaults or override given", func() {
Expect(output).To(BeEmpty())
})

When("default values are provided", func() {
BeforeEach(func() {
compiler = compiler.Defaults(map[string]string{
"foo": "bar",
})
})

It("puts the default in the output", func() {
Expect(output).To(HaveKeyWithValue("foo", "bar"))
})
})

When("default values are provided twice", func() {
var oldCompiler labels.Compiler

BeforeEach(func() {
oldCompiler = compiler.Defaults(map[string]string{
"foo": "bar",
"hello": "there",
})
compiler = oldCompiler.Defaults(map[string]string{
"foo": "baz",
})
})

It("puts the latest default in the output", func() {
Expect(output).To(HaveKeyWithValue("foo", "baz"))
Expect(output).To(HaveKeyWithValue("hello", "there"))
})

It("is immutable", func() {
Expect(oldCompiler.Compile(nil)).To(HaveKeyWithValue("foo", "bar"))
Expect(oldCompiler.Compile(nil)).To(HaveKeyWithValue("hello", "there"))
})
})

When("a default value is overridden", func() {
BeforeEach(func() {
compiler = compiler.Defaults(map[string]string{
"foo": "bar",
})
override = map[string]string{
"foo": "baz",
}
})

It("will use overridden value", func() {
Expect(output).To(HaveKeyWithValue("foo", "baz"))
})

It("will not accidently store the override", func() {
Expect(compiler.Compile(nil)).To(HaveKeyWithValue("foo", "bar"))
})
})
})
10 changes: 3 additions & 7 deletions controllers/controllers/workloads/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,11 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/pod-security-admission/api"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate

func createOrPatchNamespace(ctx context.Context, client client.Client, log logr.Logger, orgOrSpace client.Object, labels map[string]string) error {
log = log.WithName("createOrPatchNamespace")

Expand All @@ -35,8 +33,6 @@ func createOrPatchNamespace(ctx context.Context, client client.Client, log logr.
for key, value := range labels {
namespace.Labels[key] = value
}
namespace.Labels[api.EnforceLevelLabel] = string(api.LevelRestricted)
namespace.Labels[api.AuditLevelLabel] = string(api.LevelRestricted)

return nil
})
Expand Down Expand Up @@ -168,14 +164,14 @@ func reconcileRoleBindings(ctx context.Context, kClient client.Client, log logr.
return nil
}

func getNamespace(ctx context.Context, log logr.Logger, client client.Client, namespaceName string) (*corev1.Namespace, bool) {
func getNamespace(ctx context.Context, log logr.Logger, client client.Client, namespaceName string) error {
log = log.WithValues("namespace", namespaceName)

namespace := new(corev1.Namespace)
err := client.Get(ctx, types.NamespacedName{Name: namespaceName}, namespace)
if err != nil {
log.Error(err, "failed to get namespace")
return nil, false
return err
}
return namespace, true
return nil
}
Loading

0 comments on commit 148d1ee

Please sign in to comment.