Skip to content
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
10 changes: 10 additions & 0 deletions class/defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,13 @@ parameters:
- syn

namespaces: {}

labelSync:
ignoreNames: []
Comment thread
DebakelOrakel marked this conversation as resolved.
ignorePrefixes:
- cilium
- kube
- openshift
- appuio
- syn
applyOnPrefix: {}
1 change: 1 addition & 0 deletions class/namespaces.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ parameters:
output_path: .
- input_paths:
- ${_base_directory}/component/main.jsonnet
- ${_base_directory}/component/espejote.jsonnet
input_type: jsonnet
output_path: ${_instance}/
76 changes: 76 additions & 0 deletions component/espejote-templates/label-sync.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
local esp = import 'espejote.libsonnet';
local context = esp.context();

// Check if the namespace should be ignored
local ignoreNamespace(namespace, config) =
if std.member(config.ignoreNames, namespace.metadata.name) then
true
else if std.length(std.filter(
function(prefix) std.startsWith(namespace.metadata.name, prefix),
config.ignorePrefixes
)) > 0 then
true
else false;

// Get the labels from a namespace name prefix by
// first finding the prefixes that match with the namespace name
// then return the labels defined for that prefix.
local labelsFromPrefix(namespace, config) =
local filteredPrefixes = std.filter(
function(prefix) std.startsWith(namespace.metadata.name, prefix),
std.objectFields(config.applyOnPrefix)
);
// If multiple prefixes define the same key, the more specific prefix wins.
local sortedPrefixes = std.sort(
filteredPrefixes,
function(obj) std.length(obj)
);
// Merge the labels from the prefixes
local mergedLabels = std.foldl(
function(acc, prefix) acc + config.applyOnPrefix[prefix],
sortedPrefixes,
{}
);

{
[key]: mergedLabels[key]
for key in std.objectFields(mergedLabels)
if mergedLabels[key] != null
};

// Reconcile the given namespace.
local reconcileNamespace(namespace, config) =
// Check if the namespace can be ignored
if ignoreNamespace(namespace, config) then []
// Apply labels if the namespace name starts with defined prefixes
else if labelsFromPrefix(namespace, config) != {} then [
namespace {
metadata+: {
labels+: labelsFromPrefix(namespace, config),
},
},
]
else [];

// check if the object is getting deleted by checking if it has
// `metadata.deletionTimestamp`.
local inDelete(obj) = std.get(obj.metadata, 'deletionTimestamp', '') != '';

// Do the thing
if esp.triggerName() == 'namespace' then (
// Handle single namespace update on namespace trigger
local nsTrigger = esp.triggerData();
// nsTrigger.resource can be null if we're called when the namespace is getting
// deleted. If it's not null, we still don't want to do anything when the
// namespace is getting deleted.
if nsTrigger.resource != null && !inDelete(nsTrigger.resource) then
reconcileNamespace(nsTrigger.resource, config)
) else (
// Reconcile all namespaces for managedresource reconcile.
local namespaces = context.namespaces;
std.flattenArrays([
reconcileNamespace(ns, config)
for ns in namespaces
if !inDelete(ns)
])
)
124 changes: 124 additions & 0 deletions component/espejote.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
local com = import 'lib/commodore.libjsonnet';
local esp = import 'lib/espejote.libsonnet';
local kap = import 'lib/kapitan.libjsonnet';
local kube = import 'lib/kube.libjsonnet';
local utils = import 'utils.libsonnet';

// The hiera parameters for the component
local inv = kap.inventory();
local params = inv.parameters.namespaces;
local instanceName = inv.parameters._instance;

local espNamespace = inv.parameters.espejote.namespace;
local mrName = '%s-label-sync' % instanceName;
local rbacName = 'managedresource-%s-label-sync' % instanceName;

// RBAC for Espejote
local espejoteRBAC = [
{
apiVersion: 'v1',
kind: 'ServiceAccount',
metadata: {
labels: {
'app.kubernetes.io/component': 'rbac',
'app.kubernetes.io/name': mrName,
},
name: mrName,
namespace: espNamespace,
},
},
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'ClusterRole',
metadata: {
labels: {
'app.kubernetes.io/component': 'rbac',
'app.kubernetes.io/name': rbacName,
},
name: rbacName,
},
rules: [
{
apiGroups: [ '' ],
resources: [ 'namespaces' ],
verbs: [ 'get', 'list', 'watch', 'patch' ],
},
],
},
{
apiVersion: 'rbac.authorization.k8s.io/v1',
kind: 'ClusterRoleBinding',
metadata: {
labels: {
'app.kubernetes.io/component': 'rbac',
'app.kubernetes.io/name': rbacName,
},
name: rbacName,
},
roleRef: {
apiGroup: 'rbac.authorization.k8s.io',
kind: 'ClusterRole',
name: rbacName,
},
subjects: [
{
kind: 'ServiceAccount',
name: mrName,
namespace: espNamespace,
},
],
},
];

// Espejote resources
local managedResource = esp.managedResource(mrName, espNamespace) {
metadata+: {
annotations: {
'syn.tools/description': |||
Manages labels of namespaces based on namespace prefixes.
See https://hub.syn.tools/namespaces/index.html for details.
|||,
},
},
spec: {
context: [
{
name: 'namespaces',
resource: {
apiVersion: 'v1',
kind: 'Namespace',
},
},
],
triggers: [
{
name: 'namespace',
watchContextResource: {
name: 'namespaces',
},
},
],
serviceAccountRef: {
name: espejoteRBAC[0].metadata.name,
},
template: ('local config = %s;\n' % std.manifestJson(params.labelSync))
+ (importstr 'espejote-templates/label-sync.jsonnet'),
},
};

// Check if espejote is installed and resources are configured
local hasEspejote = std.member(inv.applications, 'espejote');
local hasDynamicLabels = std.length(params.labelSync.applyOnPrefix) > 0;

// Define outputs below
if hasDynamicLabels && hasEspejote then
{
'00_espejote_rbac': espejoteRBAC,
'00_espejote_mr': managedResource,
}
else if hasDynamicLabels then
std.trace(
'espejote must be installed',
{}
)
else {}
120 changes: 120 additions & 0 deletions docs/modules/ROOT/pages/how-tos/using-label-sync.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
= Using LabelSync on Namespace Prefixes

Whit this component you can label namespaces based on their prefixes.

== Customizing Labels and Prefixes

You can define sets of labels that should be applied on namespaces that match the defined prefixes.

[TIP]
====
All labels from all matched prefixes get applied, if a label key is defined multiple times the longest prefix has precedence.
====

[source,yaml]
----
labelSync:
applyOnPrefix:
vshn-postgres: <1>
set.rbac.syn.tools/allow-team2: '' <2>
syn.tools/environment: test <2>
vshn-postgres-test:
set.rbac.syn.tools/allow-team2: null <3>
set.rbac.syn.tools/allow-team3: ''
vshn-postgres-prod:
syn.tools/environment: prod <4>
----
<1> Prefix that will be matched to namespaces.
<2> Defines the label that will be applied if the prefix matches.
<3> Labels with a value of null will be removed, eg. if a less precise prefix match adds this label.
<4> Overwrites the value of a label, eg. if a less precise prefix match has a different value.

== Examples

Using above configuration, here are a couple of examples for better understanding.

=== Overwriting Labels

If you have the following namespaces:

[source,yaml]
----
apiVersion: v1
kind: Namespace
metadata:
name: vshn-postgres-abc
---
apiVersion: v1
kind: Namespace
metadata:
name: vshn-postgres-prod-abc
----

Then the following labels will be applied:

[source,yaml]
----
apiVersion: v1
kind: Namespace
metadata:
labels:
set.rbac.syn.tools/allow-team2: ''
syn.tools/environment: test
name: vshn-postgres-abc
---
apiVersion: v1
kind: Namespace
metadata:
labels:
set.rbac.syn.tools/allow-team2: ''
syn.tools/environment: prod <1>
name: vshn-postgres-prod-abc
----
<1> This label got overwritten by the more precise prefix match.

=== Removing Labels


If you have the following namespaces:

[source,yaml]
----
apiVersion: v1
kind: Namespace
metadata:
name: vshn-postgres-abc
---
apiVersion: v1
kind: Namespace
metadata:
name: vshn-postgres-test-abc
----

Then the following labels will be applied:

[source,yaml]
----
apiVersion: v1
kind: Namespace
metadata:
labels:
set.rbac.syn.tools/allow-team2: ''
syn.tools/environment: test
name: vshn-postgres-abc
---
apiVersion: v1
kind: Namespace
metadata:
labels:
set.rbac.syn.tools/allow-team3: '' <1> <2>
syn.tools/environment: test
name: vshn-postgres-test-abc
----
<1> This label was added by the more precise prefix match.
<2> The label `set.rbac.syn.tools/allow-team2` was removed by the rule:
+
[source,yaml]
----
vshn-postgres-test:
set.rbac.syn.tools/allow-team2: null
----
Loading
Loading