fix(plugin): reconcile kubernetes namespace labels

This commit is contained in:
Dotta
2026-05-12 18:01:11 -05:00
committed by Chris Farhood
parent b248acd46c
commit 94fc81266f
2 changed files with 79 additions and 20 deletions
@@ -41,34 +41,48 @@ export async function ensureTenant(clients: KubeClients, input: EnsureTenantInpu
}
async function ensureNamespace(clients: KubeClients, input: EnsureTenantInput): Promise<void> {
const manifest = buildNamespaceManifest(input);
try {
await clients.core.readNamespace({ name: input.namespace });
const existing = await clients.core.readNamespace({ name: input.namespace });
await clients.core.replaceNamespace({
name: input.namespace,
body: withResourceVersion(buildNamespaceManifest(input, existing), existing) as never,
});
return;
} catch (err) {
if (!isNotFound(err)) throw err;
}
try {
await clients.core.createNamespace({
body: {
apiVersion: "v1",
kind: "Namespace",
metadata: {
name: input.namespace,
labels: {
"paperclip.io/company-id": input.companyId,
"paperclip.io/managed-by": "paperclip-k8s-plugin",
"pod-security.kubernetes.io/enforce": "restricted",
"pod-security.kubernetes.io/audit": "restricted",
"pod-security.kubernetes.io/warn": "restricted",
},
},
},
});
await clients.core.createNamespace({ body: manifest });
} catch (err) {
if (!isAlreadyExists(err)) throw err;
const existing = await clients.core.readNamespace({ name: input.namespace });
await clients.core.replaceNamespace({
name: input.namespace,
body: withResourceVersion(buildNamespaceManifest(input, existing), existing) as never,
});
}
}
function buildNamespaceManifest(input: EnsureTenantInput, existing?: unknown): Record<string, unknown> {
const existingLabels = (existing as { metadata?: { labels?: Record<string, string> } })?.metadata?.labels ?? {};
return {
apiVersion: "v1",
kind: "Namespace",
metadata: {
name: input.namespace,
labels: {
...existingLabels,
"paperclip.io/company-id": input.companyId,
"paperclip.io/managed-by": "paperclip-k8s-plugin",
"pod-security.kubernetes.io/enforce": "restricted",
"pod-security.kubernetes.io/audit": "restricted",
"pod-security.kubernetes.io/warn": "restricted",
},
},
};
}
async function ensureServiceAccount(clients: KubeClients, input: EnsureTenantInput): Promise<void> {
const manifest = {
apiVersion: "v1",
@@ -24,6 +24,7 @@ function makeMockClients() {
createNamespacedLimitRange: track("LimitRange"),
replaceNamespacedLimitRange: track("LimitRangeReplace"),
readNamespace: vi.fn().mockRejectedValue({ code: 404 }),
replaceNamespace: track("NamespaceReplace"),
},
rbac: {
readNamespacedRole: vi.fn().mockRejectedValue({ code: 404 }),
@@ -97,17 +98,39 @@ describe("ensureTenant", () => {
expect(sa.metadata.annotations["eks.amazonaws.com/role-arn"]).toBe("arn:aws:iam::123:role/paperclip");
});
it("does not recreate a namespace that already exists", async () => {
it("reconciles a namespace that already exists", async () => {
const clients = makeMockClients();
clients.core.readNamespace.mockResolvedValue({ body: { metadata: { name: baseInput.namespace } } });
clients.core.readNamespace.mockResolvedValue({
metadata: {
name: baseInput.namespace,
resourceVersion: "rv-namespace",
labels: { "operator.example.com/team": "infra" },
},
});
await ensureTenant(clients as never, baseInput);
expect(clients.core.createNamespace).not.toHaveBeenCalled();
expect(clients.core.replaceNamespace).toHaveBeenCalledWith({
name: baseInput.namespace,
body: expect.objectContaining({
metadata: expect.objectContaining({
resourceVersion: "rv-namespace",
labels: expect.objectContaining({
"operator.example.com/team": "infra",
"paperclip.io/company-id": baseInput.companyId,
"paperclip.io/managed-by": "paperclip-k8s-plugin",
"pod-security.kubernetes.io/enforce": "restricted",
"pod-security.kubernetes.io/audit": "restricted",
"pod-security.kubernetes.io/warn": "restricted",
}),
}),
}),
});
});
it("reconciles existing managed resources with the latest desired manifests", async () => {
const clients = makeMockClients();
const existing = { metadata: { resourceVersion: "rv-1" } };
clients.core.readNamespace.mockResolvedValue({ metadata: { name: baseInput.namespace } });
clients.core.readNamespace.mockResolvedValue({ metadata: { name: baseInput.namespace, resourceVersion: "rv-ns" } });
clients.core.readNamespacedServiceAccount.mockResolvedValue(existing);
clients.rbac.readNamespacedRole.mockResolvedValue(existing);
clients.rbac.readNamespacedRoleBinding.mockResolvedValue(existing);
@@ -140,6 +163,18 @@ describe("ensureTenant", () => {
}),
);
expect(clients.networking.replaceNamespacedNetworkPolicy).toHaveBeenCalled();
expect(clients.core.replaceNamespace).toHaveBeenCalledWith(
expect.objectContaining({
body: expect.objectContaining({
metadata: expect.objectContaining({
resourceVersion: "rv-ns",
labels: expect.objectContaining({
"pod-security.kubernetes.io/enforce": "restricted",
}),
}),
}),
}),
);
});
it("removes stale standard egress NetworkPolicy when cilium mode is selected", async () => {
@@ -155,6 +190,9 @@ describe("ensureTenant", () => {
const clients = makeMockClients();
const existing = { metadata: { resourceVersion: "rv-race" } };
clients.core.createNamespace.mockRejectedValueOnce({ code: 409 });
clients.core.readNamespace
.mockRejectedValueOnce({ code: 404 })
.mockResolvedValue({ metadata: { resourceVersion: "rv-namespace-race" } });
clients.core.readNamespacedServiceAccount
.mockRejectedValueOnce({ code: 404 })
.mockResolvedValue(existing);
@@ -163,6 +201,13 @@ describe("ensureTenant", () => {
await ensureTenant(clients as never, baseInput);
expect(clients.core.createNamespace).toHaveBeenCalled();
expect(clients.core.replaceNamespace).toHaveBeenCalledWith(
expect.objectContaining({
body: expect.objectContaining({
metadata: expect.objectContaining({ resourceVersion: "rv-namespace-race" }),
}),
}),
);
expect(clients.core.replaceNamespacedServiceAccount).toHaveBeenCalledWith(
expect.objectContaining({
body: expect.objectContaining({