Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4409114
init
albi005 Aug 18, 2026
47395fc
wip
albi005 Aug 18, 2026
f42cd0c
wip
albi005 Aug 18, 2026
e4de61b
fast install/import script
albi005 Aug 19, 2026
8d4c9bc
argocd+cdk8s demo
albi005 Aug 19, 2026
fc209ee
cdk8s CMP: root-level cdk8s.yaml, per-app app.ts, nixery sidecar
albi005 Aug 19, 2026
4d46872
add dev storage classes
albi005 Aug 19, 2026
83b6a28
add dev test script
albi005 Aug 19, 2026
b7e139f
plan
albi005 Sep 18, 2026
90071a5
ai slop: implement PLAN.md
albi005 Sep 18, 2026
5344f92
ai slop: clean up
albi005 Sep 18, 2026
fb96d47
ai slop: in-cluster git server + pod resources for local cluster
albi005 Sep 18, 2026
9d33630
ai slop: detect local via ARGOCD_APP_SOURCE_REPO_URL; namespace demo
albi005 Sep 18, 2026
8378dc5
ai slop: detect local cluster via reachable in-cluster git server
albi005 Sep 18, 2026
4e9a941
ai slop
albi005 Sep 19, 2026
75bb34e
clean up
albi005 Sep 19, 2026
60e5ce4
clean up
albi005 Sep 19, 2026
4763c60
wip
albi005 Sep 19, 2026
d59d21d
wip
albi005 Sep 19, 2026
c2da2a4
format
albi005 Sep 19, 2026
5134c38
clean up
albi005 Sep 19, 2026
ceeb4f7
simplify
albi005 Sep 19, 2026
4bc517a
simplify
albi005 Sep 20, 2026
c38060d
clean up
albi005 Sep 20, 2026
8c931a4
wip
albi005 Sep 20, 2026
024b970
more cleanup
albi005 Sep 20, 2026
2c1e49c
wip
albi005 Sep 20, 2026
83e4099
add shell-utils.ts
albi005 Sep 20, 2026
5bc1aa9
fix import, refactor
albi005 Sep 20, 2026
29a9b59
clean up
albi005 Sep 20, 2026
c46ed0c
add formatter note
albi005 Sep 20, 2026
d75666e
cluster name
albi005 Sep 20, 2026
b081132
main
albi005 Sep 20, 2026
68d37fc
fix
albi005 Sep 20, 2026
68d5602
docs
albi005 Sep 20, 2026
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
42 changes: 42 additions & 0 deletions .dev/cdk8s-download-patch.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/**
* Patch cdk8s download() https://github.com/cdk8s-team/cdk8s-cli/blob/7d810de7cbd34d1729e35192c05a3bf00ac33dd7/src/util.ts#L164
* to fix redirect handling and add caching.
*/
import { createHash } from "node:crypto";
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";

const cacheDir = process.env.CDK8S_IMPORT_CACHE ?? join(homedir(), ".cache", "cdk8s-imports");

/**
* Must be called with the same `require` used to load cdk8s-cli so we mutate
* the exact module instance its crd.js / k8s.js call into.
*/
export function patchCdk8sDownload(require: NodeJS.Require): void {
// Resolved relative to the `require` passed in (createRequire lives in
// .dev/cdk8s-import-one.ts), so `../node_modules` is the repo root.
const util = require("../node_modules/cdk8s-cli/lib/util");
const original = util.download;
util.download = async (url: string): Promise<string> => {
// Let the original function handle non-https requests
if (!/^https?:/i.test(url)) {
return original(url);
}

// Check the cache
const cacheKey = createHash("sha256").update(url).digest("hex");
const cacheFilePath = join(cacheDir, cacheKey);
if (existsSync(cacheFilePath)) {
return readFileSync(cacheFilePath, "utf-8");
}

// No cache, request and store in cache
const res = await fetch(url, { redirect: "follow" });
if (!res.ok) throw new Error(`${res.status} ${res.statusText}: ${url}`);
const text = await res.text();
mkdirSync(cacheDir, { recursive: true });
writeFileSync(cacheFilePath, text);
return text;
};
}
25 changes: 25 additions & 0 deletions .dev/cdk8s-import-one.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Usage: bun .dev/cdk8s-import-one.ts <spec>
*/
import { createRequire } from "node:module";
import { patchCdk8sDownload } from "./cdk8s-download-patch";

const require = createRequire(import.meta.url);
patchCdk8sDownload(require);

const { matchImporter } = require("../node_modules/cdk8s-cli/lib/import/dispatch");

const spec = process.argv[2];

const importSpec = { source: spec, moduleNamePrefix: undefined as string | undefined };
const importer = await matchImporter(importSpec, { exclude: [] });
if (!importer) throw new Error(`unable to determine import type for "${spec}"`);

process.stderr.write(`Importing ${spec}...\n`);
await importer.import({
moduleNamePrefix: importSpec.moduleNamePrefix,
targetLanguage: "typescript",
outdir: "imports",
classNamePrefix: undefined,
});
process.exit(0);
41 changes: 41 additions & 0 deletions .dev/cdk8s-import.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* Default `cdk8s import` is slow and has a bug when receiving and redirect that makes it even slower.
* This script runs each import in parallel and patches cdk8s's download().
*/
import { $ } from "bun";
import { createHash } from "node:crypto";
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, symlinkSync } from "node:fs";
import { join } from "node:path";
import { homedir } from "node:os";
import { parse as parseYaml } from "yaml";

const cacheDir = process.env.CDK8S_IMPORT_CACHE ?? join(homedir(), ".cache", "cdk8s-imports");

const cdk8sConfig = parseYaml(readFileSync("cdk8s.yaml", "utf-8")) as { imports: string[] };
const cdk8sImports = cdk8sConfig.imports;
const importsDir = "imports";

// Reuse cached imports dir if cdk8s.yaml hasn't changed
const cdk8sConfigHash = createHash("sha256").update(readFileSync("cdk8s.yaml")).digest("hex");
const cachedImportsDir = join(cacheDir, "out", cdk8sConfigHash, "imports");
if (existsSync(cachedImportsDir)) {
rmSync(importsDir, { recursive: true, force: true });
symlinkSync(cachedImportsDir, importsDir, "dir");
process.exit(0);
}

// cdk8s.yaml changed, create new imports dir
rmSync(importsDir, { recursive: true, force: true });
const started = Date.now();

const shellOutputs = await Promise.all(cdk8sImports.map((spec) => $`bun .dev/cdk8s-import-one.ts ${spec}`.nothrow()));

const failed = shellOutputs.filter((result) => result.exitCode !== 0).length;
const wall = ((Date.now() - started) / 1000).toFixed(1);
console.error(`${cdk8sImports.length - failed}/${cdk8sImports.length} imports OK in ${wall}s`);
if (failed) process.exit(1);

// Cache
mkdirSync(cachedImportsDir, { recursive: true });
cpSync(importsDir, cachedImportsDir, { recursive: true });
console.error(`cached -> ${cachedImportsDir}`);
39 changes: 39 additions & 0 deletions .dev/cdk8s-synth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Render a single cdk8s Application: ./APP_NAME/app.ts.
*
* `app.ts` default-exports a crafted `App` (see PLAN.md); this script just
* imports it and calls `.synth()`. The output directory is passed through the
* `CDK8S_OUTDIR` env var, which `new App()` picks up, so app.ts stays free of
* build plumbing.
*
* Usage: bun .dev/cdk8s-synth.ts APP_NAME
*
* ArgoCD runs this per cdk8s Application via the Config Management Plugin
* sidecar; `dist/APP_NAME/*.k8s.yaml` is what gets applied.
*/
import { existsSync } from "node:fs";
import { join, resolve } from "node:path";

const appName = process.argv[2];
if (!appName) {
console.error("usage: bun run cdk8s:synth APP_NAME");
process.exit(1);
}

const outDir = join("dist", appName);
process.env.CDK8S_OUTDIR = outDir;

const appPath = resolve(import.meta.dir, "..", appName, "app.ts");
if (!existsSync(appPath)) {
console.error(`✗ ${appPath} does not exist`);
process.exit(1);
}

const module = (await import(appPath)) as { default?: { synth(): void } };
if (!module.default || typeof module.default.synth !== "function") {
console.error(`✗ ${appPath} must default-export a cdk8s App`);
process.exit(1);
}

module.default.synth();
console.error(`✓ synthesized ${appName} -> ${outDir}`);
12 changes: 12 additions & 0 deletions .dev/cdk8s-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { App, Chart } from "cdk8s";
import { Construct } from "constructs";

// Helper for cdk8s apps that can only have a single instance in the cluster.
//
// Always specify resource names so that cdk8s doesn't generate it.
export function singletonApp(namespace: string, factory: (scope: Construct) => void): App {
const app = new App();
const chart = new Chart(app, "chart", { namespace });
factory(chart);
return app;
}
21 changes: 21 additions & 0 deletions .dev/dev-storage-classes.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Apply these when running the cluster in a dev environment
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: node-local-zfs
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: memory-hdd
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: memory-ssd
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
18 changes: 18 additions & 0 deletions .dev/environment.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
export const environment: "Development" | "Production" = ((e) => {
if (e == "Development" || e == "Production") return e;
if (!e) return "Development";
throw new Error("Invalid KIRDEV_ENVIRONMENT.");
})(process.env.KIRDEV_ENVIRONMENT);

export const k8sRepoUrl =
process.env.KIRDEV_K8S_REPO_URL ??
(() => {
if (environment == "Development") {
return `git://git-server.argocd.svc.cluster.local:9418/k8s.git`;
}
throw new Error("KIRDEV_K8S_REPO_URL not set.");
})();

export const k8sRepoPort = process.env.KIRDEV_K8S_REPO_PORT;

export const k8sRepoRevision = process.env.KIRDEV_K8S_REPO_REVISION;
80 changes: 80 additions & 0 deletions .dev/git-server.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
apiVersion: v1
kind: Service
metadata:
name: git-server
namespace: argocd
spec:
selector:
app: git-server
ports:
- port: 9418
targetPort: 9418
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: git-server-repos
namespace: argocd
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 100Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: git-server
namespace: argocd
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: git-server
template:
metadata:
labels:
app: git-server
spec:
containers:
- name: git
image: nixery.dev/shell/git
command: [ bash, -c ]
args:
- |
set -e
if [ ! -d /srv/k8s.git ]; then
git init --bare --initial-branch=main /srv/k8s.git
fi
git -C /srv/k8s.git config daemon.receivepack true
exec git daemon --reuseaddr --export-all --enable=receive-pack --base-path=/srv --listen=0.0.0.0 --port=9418
ports:
- containerPort: 9418
readinessProbe:
exec:
command:
- git
- ls-remote
- git://127.0.0.1:9418/k8s.git
initialDelaySeconds: 2
periodSeconds: 10
resources:
requests:
cpu: 10m
memory: 32Mi
ephemeral-storage: 0
limits:
cpu: 200m
memory: 128Mi
ephemeral-storage: 200Mi
volumeMounts:
- name: repos
mountPath: /srv
volumes:
- name: repos
persistentVolumeClaim:
claimName: git-server-repos
10 changes: 10 additions & 0 deletions .dev/kubectl-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { $ } from "bun";
import { App } from "cdk8s";

export async function kubectlApplyYaml(yaml: string) {
await $`kubectl apply -f - < ${Buffer.from(yaml)}`;
}

export async function kubectlApplyCdk8sApp(app: App) {
await kubectlApplyYaml(app.synthYaml());
}
Loading