diff --git a/.dev/dev-storage-classes.yaml b/.dev/dev-storage-classes.yaml new file mode 100644 index 0000000..b6765d5 --- /dev/null +++ b/.dev/dev-storage-classes.yaml @@ -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 \ No newline at end of file diff --git a/.gitignore b/.gitignore index 1b7532e..1ab952a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,7 @@ .idea -**/charts \ No newline at end of file +**/charts +.junie + +imports +dist/ +node_modules diff --git a/PLAN.md b/PLAN.md new file mode 100644 index 0000000..bb8e761 --- /dev/null +++ b/PLAN.md @@ -0,0 +1,23 @@ +we are working on a comprehensive GitOps setup. + +the current version uses ArgoCD with each ArgoCD Application stored in a top-level directory in the repo, +as either classic K8s .yaml files or a kustomize.yaml. + +i want to add support for cdk8s-based apps, with automatic update PRs using Renovate. +additionally i want to be able to deploy a cluster locally for testing. + +## plan + +- [x] keep the same top-level setup and add support for cdk8s apps using a `app.ts` file as the entry-point + (done via an ArgoCD Config Management Plugin sidecar, see `argocd/kustomization.yaml`; sample in `demo/`) +- [ ] the current setup uses some Renovate magic to open PRs from other repositories from GitHub Actions. + see kir-dev/k8s and kir-dev/StartSCH (both already in ~/src). + create a script that can be run from the CI/CD pipelines of other repositories that opens an update PR for that specific app. + +## rules +- top-level directories other than the ones starting with a `.` are ArgoCD Applications. + keep non-Application files either in a dir like `.cdk8s` (or something else) or at the top-level. +- use bun. don't add dependencies unless necessary. + +## notes +- there is a very basic cdk8s sample in ~/Desktop/f using bun diff --git a/README.md b/README.md index b7c17ea..436c756 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + +TODO: Update README + # Kir-Dev Kubernetes configuration ## Bootstrapping @@ -39,10 +42,35 @@ Create a new directory containing [`helmCharts:`](https://kubectl.docs.kubernetes.io/references/kustomize/builtins/#_helmchartinflationgenerator_) to install Helm charts. Set values either using `valuesInline:` or by creating a `values.yaml` and referencing it using `valuesFile:`. +- a cdk8s app: a `cdk8s.yaml` + `app.ts` (see below). ArgoCD checks each directory (except the ones starting with a `.`). If it sees `kustomization.yaml`, it `kubectl apply --kustomize`s it, otherwise it applies `.yaml` files using `kubectl apply`. +### cdk8s apps + +Directories containing an `app.ts` are synthesized with cdk8s by an ArgoCD +[Config Management Plugin](https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/) +(sidecar on the repo-server, see `argocd/kustomization.yaml`). There is a single +cdk8s project for the whole repo: the shared `cdk8s.yaml` (imports) and +`package.json` (deps) live at the top level, and each cdk8s app is just a +directory with an `app.ts`: + +``` +/ + cdk8s.yaml # language: typescript + imports (shared) + package.json # deps: cdk8s, constructs; devDeps: cdk8s-cli (shared) + imports/ # generated (gitignored) + my-app/ + app.ts # the cdk8s app entrypoint (Chart(s) + app.synth()) +``` + +`app.ts` imports from `../imports/k8s`. The CMP runs from the repo root: +`bun install`, `cdk8s import`, then synthesizes *only that app's* `app.ts` into a +per-app `dist//` and feeds `dist//*.k8s.yaml` to ArgoCD. The sidecar +image is `nixery.dev/bun/kubernetes-helm/bash/coreutils/cacert/nodejs` (nixery +bundles `bun`, `helm`, `node`, and a shell). See `demo/` for a minimal example. + ## Documentation - https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/ diff --git a/argocd/kustomization.yaml b/argocd/kustomization.yaml index dc416e7..d4b8838 100644 --- a/argocd/kustomization.yaml +++ b/argocd/kustomization.yaml @@ -22,6 +22,40 @@ helmCharts: # https://argo-cd.readthedocs.io/en/stable/user-guide/kustomize/#kustomizing-helm-charts kustomize.buildOptions: "--enable-helm" + # Sidecar Config Management Plugin for cdk8s apps. + # Top-level directories containing an `app.ts` are synthesized with cdk8s. + # The shared `cdk8s.yaml` + `package.json` live at the repo root, so the + # generate step cd's to the root, installs deps, (re)generates `imports/`, + # then synthesizes only this app's `app.ts` into a per-app output dir. + # https://argo-cd.readthedocs.io/en/stable/operator-manual/config-management-plugins/ + cmp: + create: true + plugins: + cdk8s: + discover: + fileName: "app.ts" + generate: + command: [bash, -c] + args: + - | + set -e + app_dir="$(pwd)" + root_dir="$(cd .. && pwd)" + out_dir="$root_dir/dist/$(basename "$app_dir")" + cd "$root_dir" + # copyfile backend -> real node_modules (no symlinks into bun's + # global cache, which breaks module resolution from nested dirs) + if [ -f bun.lock ]; then bun install --frozen-lockfile --backend=copyfile >&2; else bun install --backend=copyfile >&2; fi + bun run import >&2 + # materialize the imports symlink (import script caches via symlink) + if [ -L imports ]; then + target="$(readlink imports)" + rm imports + cp -rL "$target" imports + fi + ./node_modules/.bin/cdk8s synth --app "bun $app_dir/app.ts" --output "$out_dir" >&2 + cat "$out_dir"/*.k8s.yaml + global: networkPolicy: create: true @@ -109,6 +143,42 @@ helmCharts: cpu: 50m memory: 32Mi ephemeral-storage: 50Mi + + # Sidecar running the `cdk8s` CMP. Needs `bun` (run/synth cdk8s apps), + # `nodejs` (cdk8s-cli bin is a node script), `helm` (cdk8s import pulls + # helm charts), plus a shell + coreutils and ca-certificates. + # Provided by nixery: https://nixery.dev/ + extraContainers: + - name: cmp-cdk8s + command: ["/var/run/argocd/argocd-cmp-server"] + image: nixery.dev/bun/kubernetes-helm/bash/coreutils/cacert/nodejs + imagePullPolicy: IfNotPresent + env: + # bun needs a writable HOME for its install cache; /tmp is the cmp-tmp emptyDir. + - name: HOME + value: /tmp + securityContext: + runAsNonRoot: true + runAsUser: 999 + volumeMounts: + - mountPath: /var/run/argocd + name: var-files + - mountPath: /home/argocd/cmp-server/plugins + name: plugins + - mountPath: /home/argocd/cmp-server/config/plugin.yaml + subPath: cdk8s.yaml + name: argocd-cmp-cm + # Do not share the repo-server's /tmp volume (path traversal mitigation). + - mountPath: /tmp + name: cmp-tmp + + volumes: + - name: argocd-cmp-cm + configMap: + name: argocd-cmp-cm + - name: cmp-tmp + emptyDir: {} + resources: limits: cpu: 700m diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..bb5ac0e --- /dev/null +++ b/bun.lock @@ -0,0 +1,335 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "dependencies": { + "cdk8s": "^2.70.93", + "cdk8s-plus-33": "^2.5.49", + "constructs": "^10.8.1", + }, + "devDependencies": { + "cdk8s-cli": "^2.207.53", + }, + }, + }, + "packages": { + "@jsii/check-node": ["@jsii/check-node@1.139.0", "", { "dependencies": { "chalk": "^4.1.2", "semver": "^7.8.5" } }, "sha512-tLS0H2XPN3hWVO9OtVB2DDllaxz3M6Jz8Zk8ByV1pl2h5b6LayJg8lHYvRMf0b3P/82xqWat7CpfLeqU96OwVw=="], + + "@jsii/spec": ["@jsii/spec@1.139.0", "", {}, "sha512-+l1B0h4WAg6KOQ7PGykW/FvxeFmyt74U0/n41cus9Jdjv3VfQJKynsv5r9Ng23B1KwWqJkS5YvJuv2yGYoULdw=="], + + "@nodelib/fs.scandir": ["@nodelib/fs.scandir@2.1.5", "", { "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" } }, "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g=="], + + "@nodelib/fs.stat": ["@nodelib/fs.stat@2.0.5", "", {}, "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="], + + "@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], + + "@types/node": ["@types/node@16.18.126", "", {}, "sha512-OTcgaiwfGFBKacvfwuHzzn1KLxH/er8mluiy8/uM3sGXHaRe73RrSIj01jow9t4kJEW633Ov+cOexXeiApTyAw=="], + + "@xmldom/xmldom": ["@xmldom/xmldom@0.9.11", "", {}, "sha512-tW8bcK3hsG0/uqSnNz6TK4BkcuZSezoU7DlnYssILmZDktPnSHHuDJJFM0AJv+13gz2r0iGdrj6qqKeUnxXEDg=="], + + "ajv": ["ajv@8.20.0", "", { "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2" } }, "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA=="], + + "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], + + "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="], + + "astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="], + + "at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="], + + "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="], + + "brace-expansion": ["brace-expansion@2.1.4", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg=="], + + "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], + + "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="], + + "case": ["case@1.6.3", "", {}, "sha512-mzDSXIPaFwVDvZAHqZ9VlbyF4yyXRuX6IvB06WvPYkqJVO24kX1PPhv9bfpKNFZyxYFmmgo03HUiD8iklmJYRQ=="], + + "cdk8s": ["cdk8s@2.70.93", "", { "dependencies": { "fast-json-patch": "^3.1.1", "follow-redirects": "^1.16.0", "yaml": "2.9.0" }, "peerDependencies": { "constructs": "^10" } }, "sha512-MKla1n0DkrvxANYizpsC7FoylpGv2W5dn2SMsfKFkCVBm3QTQd9QyWnRS5ldsv+C6fj5mbeID/i/D/aYzeRJpA=="], + + "cdk8s-cli": ["cdk8s-cli@2.207.53", "", { "dependencies": { "@types/node": "^16", "ajv": "^8.20.0", "cdk8s": "^2.70.93", "cdk8s-plus-33": "^2.5.49", "codemaker": "^1.139.0", "colors": "1.4.0", "constructs": "^10.8.1", "fs-extra": "^8", "jsii-pacmak": "^1.139.0", "jsii-rosetta": "^5.9.62", "jsii-srcmak": "0.1.1319", "json2jsii": "0.5.19", "semver": "^7.8.5", "sscaff": "^1.2.274", "table": "^6.9.0", "yaml": "2.9.0", "yargs": "^15" }, "bin": { "cdk8s": "bin/cdk8s" } }, "sha512-gCb91zgMRp1fI0fOo+G1gFkPqKT7+52Cx0OGa2L5Usjb1VPBvaae72TiR0d1oqXff0aTDqJzbCz9/+R+TFyNgA=="], + + "cdk8s-plus-33": ["cdk8s-plus-33@2.5.49", "", { "dependencies": { "minimatch": "^9.0.9" }, "peerDependencies": { "cdk8s": "^2.68.11", "constructs": "^10.3.0" } }, "sha512-DUjrSCjPhA/aolVyYd5do+7SNSilym2xEWOEfcs1QaSXKP2mut1jbaNdqpVDgIyqgoIIdxKCLplQlL8rKV7IoA=="], + + "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], + + "cliui": ["cliui@6.0.0", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ=="], + + "clone": ["clone@2.1.2", "", {}, "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w=="], + + "codemaker": ["codemaker@1.139.0", "", { "dependencies": { "camelcase": "^6.3.0", "decamelize": "^5.0.1", "fs-extra": "^10.1.0" } }, "sha512-v93CwWcepdmHDENEJuPepaOzo2J8SZ5HKNVgH/+6vPrvLOoDDfVXEkBMr6KUu4trHN7QvZjFwxJBpjCYCBG9+g=="], + + "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="], + + "color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="], + + "colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="], + + "commonmark": ["commonmark@0.31.2", "", { "dependencies": { "entities": "~3.0.1", "mdurl": "~1.0.1", "minimist": "~1.2.8" }, "bin": { "commonmark": "bin/commonmark" } }, "sha512-2fRLTyb9r/2835k5cwcAwOj0DEc44FARnMp5veGsJ+mEAZdi52sNopLu07ZyElQUz058H43whzlERDIaaSw4rg=="], + + "constructs": ["constructs@10.8.1", "", {}, "sha512-98yGXYyhePqPYh3cYu8nzBERmAhC0DONe3UD03okK0nehZ7hYP4wgZuf02a04+uOWxnTJ5Rpp5m0GRNpwyLGGA=="], + + "date-format": ["date-format@4.0.14", "", {}, "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "decamelize": ["decamelize@5.0.1", "", {}, "sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA=="], + + "detect-indent": ["detect-indent@5.0.0", "", {}, "sha512-rlpvsxUtM0PQvy9iZe640/IWwWYyBsTApREbA1pHOpmOUIl9MkP/U4z7vTtg4Oaojvqhxt7sdufnT0EzGaR31g=="], + + "detect-newline": ["detect-newline@2.1.0", "", {}, "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg=="], + + "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="], + + "emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], + + "entities": ["entities@3.0.1", "", {}, "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], + + "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], + + "fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="], + + "fast-json-patch": ["fast-json-patch@3.1.1", "", {}, "sha512-vf6IHUX2SBcA+5/+4883dsIjpBTqmfBjmYiWK1savxQmFk4JfBMLa7ynTYOs1Rolp/T1betJxHiGD3g1Mn8lUQ=="], + + "fast-uri": ["fast-uri@3.1.5", "", {}, "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw=="], + + "fastq": ["fastq@1.20.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw=="], + + "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="], + + "find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="], + + "flatted": ["flatted@3.4.4", "", {}, "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q=="], + + "follow-redirects": ["follow-redirects@1.16.0", "", {}, "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw=="], + + "fs-extra": ["fs-extra@8.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g=="], + + "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], + + "glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "is-extglob": ["is-extglob@2.1.1", "", {}, "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="], + + "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], + + "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="], + + "is-number": ["is-number@7.0.0", "", {}, "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="], + + "jsii": ["jsii@5.9.52", "", { "dependencies": { "@jsii/check-node": "1.139.0", "@jsii/spec": "1.139.0", "case": "^1.6.3", "chalk": "^4", "fast-deep-equal": "^3.1.3", "log4js": "^6.9.1", "semver": "^7.8.5", "semver-intersect": "^1.5.0", "sort-json": "^2.0.1", "spdx-license-list": "^6.12.0", "typescript": "~5.9", "yargs": "^17.7.3" }, "bin": { "jsii": "bin/jsii" } }, "sha512-iOdM2f2iA6Wwr4cIapdBmmBMzE8lghZLIyvRsBT/dWBxgqWoOI0D4O/o2xJETOJ37bNdDv1dZZe9W20r+r7gjQ=="], + + "jsii-pacmak": ["jsii-pacmak@1.139.0", "", { "dependencies": { "@jsii/check-node": "1.139.0", "@jsii/spec": "1.139.0", "clone": "^2.1.2", "codemaker": "^1.139.0", "commonmark": "^0.31.2", "escape-string-regexp": "^4.0.0", "fs-extra": "^10.1.0", "jsii-reflect": "^1.139.0", "semver": "^7.8.5", "spdx-license-list": "^6.11.0", "xmlbuilder": "^15.1.1", "yargs": "^17.7.3" }, "peerDependencies": { "jsii-rosetta": ">=5.9.0" }, "bin": { "jsii-pacmak": "bin/jsii-pacmak" } }, "sha512-hE+rc+RS4Qwe+MzGqG515tXueOhn6r++/L8kpX5wT18HqUoBJdOdqFOv90PYitDgNywovOE0kEd8YyV1S5Mh6Q=="], + + "jsii-reflect": ["jsii-reflect@1.139.0", "", { "dependencies": { "@jsii/check-node": "1.139.0", "@jsii/spec": "1.139.0", "chalk": "^4", "fs-extra": "^10.1.0", "oo-ascii-tree": "^1.139.0", "yargs": "^17.7.3" }, "bin": { "jsii-query": "bin/jsii-query", "jsii-tree": "bin/jsii-tree" } }, "sha512-Z8rGDzykcg6REfFV8HJZ0oms5zi8wBO4T7B5thSS4iME0osbWJP62pFn2yT6VivOESKnkFZOI3RkIKYkynPbGg=="], + + "jsii-rosetta": ["jsii-rosetta@5.9.62", "", { "dependencies": { "@jsii/check-node": "^1.139.0", "@jsii/spec": "^1.139.0", "@xmldom/xmldom": "^0.9.11", "chalk": "^4", "commonmark": "^0.31.2", "fast-glob": "^3.3.3", "jsii": "~5.9.1", "semver": "^7.8.5", "semver-intersect": "^1.5.0", "stream-json": "^1.9.1", "typescript": "~5.9", "workerpool": "^6.5.1", "yargs": "^17.7.3" }, "bin": { "jsii-rosetta": "bin/jsii-rosetta" } }, "sha512-Zvg/aZnPna8vfn00WX/V1PhmSvehonNxwb6WxwSNK2cyzehw8XmiIRiAszyKRc5NhBTk5XWvnZroBZ0EXDeEZw=="], + + "jsii-srcmak": ["jsii-srcmak@0.1.1319", "", { "dependencies": { "fs-extra": "^9.1.0", "jsii": "~5.9.3", "jsii-pacmak": "^1.113.0", "jsii-rosetta": "^5.9.3", "ncp": "^2.0.0", "yargs": "^17.7.2" }, "bin": { "jsii-srcmak": "bin/jsii-srcmak" } }, "sha512-gF/3PZX+iQDyZ2aybbWOmQR3UiYAKLWUzIuuHV/0hprctiBlbQMmZ+PPr5Ix7HuRYqT7UdlIAt06rySrqhefzg=="], + + "json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="], + + "json-schema-traverse": ["json-schema-traverse@1.0.0", "", {}, "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="], + + "json2jsii": ["json2jsii@0.5.19", "", { "dependencies": { "camelcase": "^6.3.0", "json-schema": "^0.4.0", "snake-case": "^3.0.4" } }, "sha512-MmLACPSLsINCoQETUt7/cX/jPI3jiY7waum2I+bbv9vNxR440q66FfCMlWN/8+71InA5gjzOttx+uJEef9ispg=="], + + "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], + + "locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="], + + "lodash.truncate": ["lodash.truncate@4.4.2", "", {}, "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw=="], + + "log4js": ["log4js@6.9.1", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "flatted": "^3.2.7", "rfdc": "^1.3.0", "streamroller": "^3.1.5" } }, "sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g=="], + + "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="], + + "mdurl": ["mdurl@1.0.1", "", {}, "sha512-/sKlQJCBYVY9Ers9hqzKou4H6V5UWc/M59TH2dvkt+84itfnq7uFOMLpOiOS4ujvHP4etln18fmIxA5R5fll0g=="], + + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], + + "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], + + "minimatch": ["minimatch@9.0.9", "", { "dependencies": { "brace-expansion": "^2.0.2" } }, "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg=="], + + "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "ncp": ["ncp@2.0.0", "", { "bin": { "ncp": "./bin/ncp" } }, "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA=="], + + "no-case": ["no-case@3.0.4", "", { "dependencies": { "lower-case": "^2.0.2", "tslib": "^2.0.3" } }, "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg=="], + + "oo-ascii-tree": ["oo-ascii-tree@1.139.0", "", {}, "sha512-Sv9334UvsBcvYh9xkcBtheWjH+mkhXLmuhOm+/OTGVFUCsvdVdXwDMTbYZHpJaTSbhaHScPbHqjAPECAFwlgtg=="], + + "p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="], + + "p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="], + + "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="], + + "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], + + "picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="], + + "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="], + + "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="], + + "require-from-string": ["require-from-string@2.0.2", "", {}, "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw=="], + + "require-main-filename": ["require-main-filename@2.0.0", "", {}, "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="], + + "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="], + + "rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="], + + "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], + + "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + + "semver-intersect": ["semver-intersect@1.5.0", "", { "dependencies": { "semver": "^6.3.0" } }, "sha512-BDjWX7yCC0haX4W/zrnV2JaMpVirwaEkGOBmgRQtH++F1N3xl9v7k9H44xfTqwl+yLNNSbMKosoVSTIiJVQ2Pw=="], + + "set-blocking": ["set-blocking@2.0.0", "", {}, "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="], + + "slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="], + + "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="], + + "sort-json": ["sort-json@2.0.1", "", { "dependencies": { "detect-indent": "^5.0.0", "detect-newline": "^2.1.0", "minimist": "^1.2.0" }, "bin": { "sort-json": "app/cmd.js" } }, "sha512-s8cs2bcsQCzo/P2T/uoU6Js4dS/jnX8+4xunziNoq9qmSpZNCrRIAIvp4avsz0ST18HycV4z/7myJ7jsHWB2XQ=="], + + "spdx-license-list": ["spdx-license-list@6.12.0", "", {}, "sha512-+nUYqm3aZMSHbjsthK+i/HHI2okTElCvqwUd4k8QcSk+FTGgjq+fsdFj2wZOaX6XmR2JdWhf/NeflNnvKOjcnQ=="], + + "sscaff": ["sscaff@1.2.274", "", {}, "sha512-sztRa50SL1LVxZnF1au6QT1SC2z0S1oEOyi2Kpnlg6urDns93aL32YxiJcNkLcY+VHFtVqm/SRv4cb+6LeoBQA=="], + + "stream-chain": ["stream-chain@2.2.5", "", {}, "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA=="], + + "stream-json": ["stream-json@1.9.1", "", { "dependencies": { "stream-chain": "^2.2.5" } }, "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw=="], + + "streamroller": ["streamroller@3.1.5", "", { "dependencies": { "date-format": "^4.0.14", "debug": "^4.3.4", "fs-extra": "^8.1.0" } }, "sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw=="], + + "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], + + "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="], + + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + + "table": ["table@6.9.0", "", { "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", "slice-ansi": "^4.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1" } }, "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A=="], + + "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + + "universalify": ["universalify@0.1.2", "", {}, "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="], + + "which-module": ["which-module@2.0.1", "", {}, "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ=="], + + "workerpool": ["workerpool@6.5.1", "", {}, "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA=="], + + "wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="], + + "xmlbuilder": ["xmlbuilder@15.1.1", "", {}, "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg=="], + + "y18n": ["y18n@4.0.3", "", {}, "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="], + + "yaml": ["yaml@2.9.0", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA=="], + + "yargs": ["yargs@15.4.1", "", { "dependencies": { "cliui": "^6.0.0", "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", "require-main-filename": "^2.0.0", "set-blocking": "^2.0.0", "string-width": "^4.2.0", "which-module": "^2.0.0", "y18n": "^4.0.0", "yargs-parser": "^18.1.2" } }, "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A=="], + + "yargs-parser": ["yargs-parser@18.1.3", "", { "dependencies": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ=="], + + "codemaker/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + + "jsii/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "jsii-pacmak/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + + "jsii-pacmak/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "jsii-reflect/fs-extra": ["fs-extra@10.1.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ=="], + + "jsii-reflect/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "jsii-rosetta/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "jsii-srcmak/fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="], + + "jsii-srcmak/yargs": ["yargs@17.7.3", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-GZtjxm/J/4TSxuL3FNYjCmLktBTnIw/rVmKSIyKeYAZpmJB2ig9VauCC5xsa82GNKVKDAqpOn3KVzNt0zmrU0g=="], + + "semver-intersect/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "yargs/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + + "yargs-parser/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="], + + "yargs-parser/decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="], + + "codemaker/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "codemaker/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "jsii-pacmak/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "jsii-pacmak/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "jsii-pacmak/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "jsii-pacmak/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "jsii-pacmak/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "jsii-reflect/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "jsii-reflect/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "jsii-reflect/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "jsii-reflect/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "jsii-reflect/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "jsii-rosetta/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "jsii-rosetta/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "jsii-rosetta/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "jsii-srcmak/fs-extra/jsonfile": ["jsonfile@6.2.1", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q=="], + + "jsii-srcmak/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + + "jsii-srcmak/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "jsii-srcmak/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "jsii-srcmak/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "jsii/yargs/cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], + + "jsii/yargs/y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="], + + "jsii/yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], + + "jsii-pacmak/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "jsii-reflect/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "jsii-rosetta/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "jsii-srcmak/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + + "jsii/yargs/cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="], + } +} diff --git a/cdk8s.yaml b/cdk8s.yaml new file mode 100644 index 0000000..ef24204 --- /dev/null +++ b/cdk8s.yaml @@ -0,0 +1,26 @@ +language: typescript + +# https://cdk8s.io/docs/latest/cli/import/#import-types +imports: + - k8s@1.36.0 # version from https://github.com/cdk8s-team/cdk8s/tree/master/kubernetes-schemas + + - helm:oci://ghcr.io/argoproj/argo-helm/argo-cd@9.3.4 + - helm:oci://quay.io/jetstack/charts/cert-manager@v1.19.2 + - helm:https://cloudnative-pg.github.io/charts/cloudnative-pg@0.27.0 + - helm:https://cloudnative-pg.io/charts/plugin-barman-cloud@0.4.0 + - helm:https://prometheus-community.github.io/helm-charts/kube-prometheus-stack@81.2.0 + - helm:https://grafana.github.io/helm-charts/loki@6.49.0 + - helm:https://grafana.github.io/helm-charts/tempo@1.24.3 + - helm:https://traefik.github.io/charts/traefik@39.0.0 + - helm:https://open-telemetry.github.io/opentelemetry-helm-charts/opentelemetry-collector@0.143.0 + + # NOTE: You can only have one .yaml CRD file per API group (like networking.k8s.io), + # as they don't get merged, instead the new one replaces the old one. + # https://github.com/cdk8s-team/cdk8s-cli/issues/3797 + - https://raw.githubusercontent.com/argoproj/argo-cd/v3.2.5/manifests/install.yaml + - https://github.com/cert-manager/cert-manager/releases/download/v1.19.2/cert-manager.crds.yaml + - https://raw.githubusercontent.com/cloudnative-pg/cloudnative-pg/v1.28.0/releases/cnpg-1.28.0.yaml + - https://raw.githubusercontent.com/cloudnative-pg/plugin-barman-cloud/v0.10.0/config/crd/bases/barmancloud.cnpg.io_objectstores.yaml + - https://github.com/prometheus-operator/prometheus-operator/releases/download/v0.88.0/bundle.yaml + - https://raw.githubusercontent.com/traefik/traefik/v3.6.7/docs/content/reference/dynamic-configuration/kubernetes-crd-definition-v1.yml + diff --git a/demo/app.ts b/demo/app.ts new file mode 100644 index 0000000..10b5fc5 --- /dev/null +++ b/demo/app.ts @@ -0,0 +1,60 @@ +import { Construct } from 'constructs'; +import { App, Chart } from 'cdk8s'; +import { KubeDeployment, KubeService, Quantity } from '../imports/k8s'; + +/** + * A trivial cdk8s app that renders a Deployment + Service, proving that + * ArgoCD can synthesize and apply cdk8s manifests through the `cdk8s` + * Config Management Plugin. + */ +class DemoChart extends Chart { + constructor(scope: Construct, ns: string) { + super(scope, ns); + + const labels = { app: 'demo' }; + + new KubeDeployment(this, 'demo-deployment', { + metadata: { name: 'demo', labels }, + spec: { + replicas: 2, + selector: { matchLabels: labels }, + template: { + metadata: { labels }, + spec: { + containers: [ + { + name: 'demo', + image: 'nginx:1.27.4', + ports: [{ containerPort: 80 }], + resources: { + requests: { + cpu: Quantity.fromString('10m'), + memory: Quantity.fromString('32Mi'), + }, + limits: { + cpu: Quantity.fromString('100m'), + memory: Quantity.fromString('128Mi'), + 'ephemeral-storage': Quantity.fromString('50Mi'), + }, + }, + }, + ], + }, + }, + }, + }); + + new KubeService(this, 'demo-service', { + metadata: { name: 'demo', labels }, + spec: { + selector: labels, + ports: [{ port: 80, targetPort: 80 }], + }, + }); + } +} + +const app = new App(); +new DemoChart(app, 'demo'); + +app.synth(); diff --git a/package.json b/package.json new file mode 100644 index 0000000..d588e2a --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "private": true, + "scripts": { + "import": "bun scripts/import.ts", + "postinstall": "npm run import", + "synth": "cdk8s synth" + }, + "dependencies": { + "cdk8s": "^2.70.93", + "cdk8s-plus-33": "^2.5.49", + "constructs": "^10.8.1" + }, + "devDependencies": { + "cdk8s-cli": "^2.207.53" + } +} \ No newline at end of file diff --git a/scripts/dev-test.ts b/scripts/dev-test.ts new file mode 100644 index 0000000..ff8225d --- /dev/null +++ b/scripts/dev-test.ts @@ -0,0 +1,258 @@ +#!/usr/bin/env bun +/** + * Test local changes before opening a PR. + * + * Usage: + * bun scripts/dev-test.ts # full: validate + sync current branch into ArgoCD + * bun scripts/dev-test.ts --app # only test/sync one app + * bun scripts/dev-test.ts --validate # synth + kubectl dry-run only (no git plumbing) + * bun scripts/dev-test.ts --down # stop git daemon, remove test ApplicationSet + * + * Full mode mirrors the prod ArgoCD ApplicationSet but points it at a local + * `git daemon` served from a bare repo of the current branch, so the real + * cdk8s Config Management Plugin runs against your uncommitted work. + */ +import { readdirSync, existsSync, mkdirSync, readFileSync, rmSync, statSync } from 'node:fs'; +import { join, basename, dirname } from 'node:path'; +import { spawnSync } from 'node:child_process'; + +const GIT_BASE = '/tmp/k8s-git-test'; // parent dir for the bare repo + daemon files +const BARE_REPO = join(GIT_BASE, 'k8s.git'); +const DAEMON_PID = join(GIT_BASE, 'daemon.pid'); +const APPSET_FILE = '.dev/application-set-test.yaml'; +const GIT_PORT = Number(process.env.DEV_GIT_PORT ?? 9418); +const GIT_HOST = process.env.DEV_GIT_HOST ?? 'host.k3d.internal'; + +const args = process.argv.slice(2); +const appArg = (() => { + const i = args.indexOf('--app'); + return i >= 0 ? args[i + 1] : undefined; +})(); +const validateOnly = args.includes('--validate'); +const down = args.includes('--down'); + +function sh(cmd: string, argsList: string[], opts: { stdio?: 'inherit' | 'pipe' } = {}): { ok: boolean; out: string } { + const r = spawnSync(cmd, argsList, { stdio: opts.stdio ?? 'pipe', encoding: 'utf-8' }); + return { ok: r.status === 0, out: (r.stdout ?? '') + (r.stderr ?? '') }; +} + +function kubectlReady(): boolean { + return sh('kubectl', ['cluster-info']).ok; +} + +// --- git plumbing helpers ------------------------------------------------- + +function bareRepoReady(): boolean { + return existsSync(join(BARE_REPO, 'HEAD')) && existsSync(join(BARE_REPO, 'objects')); +} + +function setupBareRepo(): void { + if (!existsSync(GIT_BASE)) mkdirSync(GIT_BASE, { recursive: true }); + if (!bareRepoReady()) { + sh('git', ['init', '--bare', BARE_REPO], { stdio: 'inherit' }); + } +} + +function pushBranch(branch: string): void { + const r = sh('git', ['push', BARE_REPO, `HEAD:${branch}`], { stdio: 'inherit' }); + if (!r.ok) { + // first push from a fresh bare repo may need an upstream set; retry explicitly + const r2 = sh('git', ['push', BARE_REPO, `refs/heads/${branch}`], { stdio: 'inherit' }); + if (!r2.ok) { + console.error(`✗ failed to push ${branch} to ${BARE_REPO}`); + process.exit(1); + } + } +} + +function daemonRunning(): boolean { + if (!existsSync(DAEMON_PID)) return false; + const pid = Number(readFileSync(DAEMON_PID, 'utf-8').trim()); + try { + process.kill(pid, 0); + return true; + } catch { + return false; + } +} + +function startDaemon(): void { + if (daemonRunning()) { + console.log(`✓ git daemon already running (pid ${readFileSync(DAEMON_PID, 'utf-8').trim()})`); + return; + } + const r = sh( + 'git', + [ + 'daemon', '--reuseaddr', '--export-all', + '--base-path=' + GIT_BASE, + '--listen=0.0.0.0', `--port=${GIT_PORT}`, + `--pid-file=${DAEMON_PID}`, + '--detach', + ], + { stdio: 'inherit' }, + ); + if (!r.ok || !daemonRunning()) { + console.error(`✗ failed to start git daemon (port ${GIT_PORT}). Try: sudo git daemon ... or DEV_GIT_PORT=`); + process.exit(1); + } + console.log(`✓ git daemon listening on 0.0.0.0:${GIT_PORT}`); +} + +function stopDaemon(): void { + if (!existsSync(DAEMON_PID)) return; + const pid = Number(readFileSync(DAEMON_PID, 'utf-8').trim()); + try { + process.kill(pid, 'SIGTERM'); + console.log(`✓ stopped git daemon (pid ${pid})`); + } catch { + console.log('git daemon already stopped'); + } + rmSync(DAEMON_PID, { force: true }); +} + +// --- ApplicationSet ------------------------------------------------------- + +function dirsToTest(): string[] { + const all = readdirSync('.').filter((d) => !d.startsWith('.') && statSync(d).isDirectory()); + return appArg ? all.filter((d) => d === appArg) : all; +} + +function appKind(dir: string): 'cdk8s' | 'kustomize' | 'raw' { + if (existsSync(join(dir, 'app.ts'))) return 'cdk8s'; + if (existsSync(join(dir, 'kustomization.yaml')) || existsSync(join(dir, 'kustomization.yml'))) return 'kustomize'; + return 'raw'; +} + +function writeAppSet(branch: string): void { + const genDirs = appArg ? ` - path: "${appArg}"` : ` - path: "*"`; + const yaml = `kind: ApplicationSet +apiVersion: argoproj.io/v1alpha1 +metadata: + name: apps-test + namespace: argocd +spec: + goTemplate: true + goTemplateOptions: [ "missingkey=error" ] + generators: + - git: + repoURL: git://${GIT_HOST}:${GIT_PORT}/k8s + revision: ${branch} + directories: +${genDirs} + template: + metadata: + name: "{{.path.basename}}" + finalizers: + - resources-finalizer.argocd.argoproj.io + spec: + project: default + source: + repoURL: git://${GIT_HOST}:${GIT_PORT}/k8s + path: "{{.path.path}}" + destination: + name: in-cluster + syncPolicy: + automated: + prune: true + selfHeal: true + syncOptions: + - ServerSideApply=true +`; + rmSync(APPSET_FILE, { force: true }); + // ensure .dev exists + if (!existsSync('.dev')) mkdirSync('.dev'); + // node:fs has no writeFileSync import above; use fs module directly + const { writeFileSync } = require('node:fs'); + writeFileSync(APPSET_FILE, yaml); + console.log(`✓ wrote ${APPSET_FILE}`); +} + +function applyAppSet(): void { + const r = sh('kubectl', ['apply', '-f', APPSET_FILE], { stdio: 'inherit' }); + if (!r.ok) process.exit(1); +} + +function deleteAppSet(): void { + const r = sh('kubectl', ['delete', '-f', APPSET_FILE, '--ignore-not-found'], { stdio: 'inherit' }); + rmSync(APPSET_FILE, { force: true }); +} + +// --- validation ----------------------------------------------------------- + +async function synthApp(dir: string): Promise { + const outDir = `dist/${basename(dir)}`; + const r = sh('bun', ['run', 'import'], { stdio: 'inherit' }); + if (!r.ok) process.exit(1); + const synth = sh('bun', ['run', 'synth', '--app', `bun ${join(dir, 'app.ts')}`, '--output', outDir], { stdio: 'inherit' }); + if (!synth.ok) process.exit(1); + return readdirSync(outDir).filter((f) => f.endsWith('.k8s.yaml')).map((f) => join(outDir, f)); +} + +async function validateDir(dir: string): Promise { + const kind = appKind(dir); + console.log(`\n── ${dir} (${kind}) ──`); + let files: string[] = []; + if (kind === 'cdk8s') { + files = await synthApp(dir); + } else if (kind === 'kustomize') { + const r = sh('kubectl', ['kustomize', '--enable-helm', dir], { stdio: 'inherit' }); + if (!r.ok) process.exit(1); + } else { + files = readdirSync(dir).filter((f) => f.endsWith('.yaml')).map((f) => join(dir, f)); + } + if (files.length === 0) return; + // try server-side dry-run (needs CRDs present), fall back to client-side + const server = sh('kubectl', ['apply', '--dry-run=server', '-f', ...files], { stdio: 'inherit' }); + if (!server.ok) { + console.log('⚠ server dry-run failed (CRDs missing?) — falling back to client dry-run'); + sh('kubectl', ['apply', '--dry-run=client', '-f', ...files], { stdio: 'inherit' }); + } +} + +// --- main ---------------------------------------------------------------- + +const branch = sh('git', ['rev-parse', '--abbrev-ref', 'HEAD']).out.trim(); + +if (down) { + stopDaemon(); + deleteAppSet(); + console.log('✓ torn down local test setup'); + process.exit(0); +} + +if (validateOnly) { + for (const dir of dirsToTest()) await validateDir(dir); + console.log('\n✓ validation done'); + process.exit(0); +} + +// full mode +if (!kubectlReady()) { + console.error('✗ no Kubernetes cluster reachable. Start k3d + vClusters first (see README).'); + process.exit(1); +} + +// 1. local validation of the app(s) under test +for (const dir of dirsToTest()) await validateDir(dir); + +// 2. serve the current branch from a local bare repo + git daemon +setupBareRepo(); +pushBranch(branch); +startDaemon(); + +// 3. point a test ApplicationSet at it +writeAppSet(branch); +applyAppSet(); + +console.log(` +────────────────────────────────────────────── +Local test setup ready. + branch: ${branch} + repo: git://${GIT_HOST}:${GIT_PORT}/k8s + ApplicationSet: ${APPSET_FILE} + +Watch sync: kubectl -n argocd get applications -w +Portals: kubectl -n argocd port-forward svc/argocd-server 8080:443 +Teardown: bun scripts/dev-test.ts --down +──────────────────────────────────────────────`); diff --git a/scripts/import-one.ts b/scripts/import-one.ts new file mode 100644 index 0000000..414f68f --- /dev/null +++ b/scripts/import-one.ts @@ -0,0 +1,31 @@ +#!/usr/bin/env bun +/** + * Worker: import a single spec. Spawned in parallel by scripts/import.ts so + * that each import's download + codegen runs on its own process (real + * multi-core parallelism; in-process Promise.all serializes sync codegen). + * + * Usage: bun scripts/import-one.ts + */ +import { createRequire } from 'node:module'; +import { patchCdk8sDownload } from './lib/fetch-download'; + +const require = createRequire(import.meta.url); +patchCdk8sDownload(require); + +const { matchImporter } = require('../node_modules/cdk8s-cli/lib/import/dispatch'); + +const spec = process.argv[2]; +const outdir = process.argv[3] ?? 'imports'; + +const importSpec = { source: spec }; +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, + outdir, + targetLanguage: 'typescript', + classNamePrefix: undefined, +}); +process.exit(0); diff --git a/scripts/import.ts b/scripts/import.ts new file mode 100644 index 0000000..35e88fd --- /dev/null +++ b/scripts/import.ts @@ -0,0 +1,77 @@ +#!/usr/bin/env bun +/** + * Fast cdk8s import: download + generate .ts, in parallel. + * + * Runs each import from cdk8s.yaml in its own worker process (real multi-core + * parallelism) and patches cdk8s-cli's buggy download() with `fetch`. An + * output-level cache makes warm runs (unchanged cdk8s.yaml) O(1). + * + * Usage: bun scripts/import.ts [outdir] + */ +import { createHash } from 'node:crypto'; +import { readFileSync, existsSync, mkdirSync, rmSync, symlinkSync, cpSync } from 'node:fs'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; +import { spawn } from 'node:child_process'; +import { parse as parseYaml } from 'yaml'; + +const CACHE_DIR = process.env.CDK8S_IMPORT_CACHE ?? join(homedir(), '.cache', 'cdk8s-imports'); +const CONCURRENCY = Number(process.env.CDK8S_IMPORT_PARALLELISM ?? 16); + +const config = parseYaml(readFileSync('cdk8s.yaml', 'utf-8')) as { imports?: string[] }; +const imports = config.imports ?? []; +const OUTDIR = process.argv[2] ?? 'imports'; + +// --- output cache: warm runs just link the previous result --- +const outputKey = createHash('sha256').update(readFileSync('cdk8s.yaml')).digest('hex'); +const cachedImports = join(CACHE_DIR, 'out', outputKey, 'imports'); +if (existsSync(cachedImports)) { + rmSync(OUTDIR, { recursive: true, force: true }); + symlinkSync(cachedImports, OUTDIR, 'dir'); + console.error(`cached (${outputKey.slice(0, 8)}): linked -> ${OUTDIR}`); + process.exit(0); +} + +// --- worker pool: one process per import, bounded concurrency --- +const started = Date.now(); +const queue = [...imports]; +let running = 0; +let failed = 0; + +function runWorker(spec: string): Promise { + return new Promise((resolve) => { + const child = spawn( + 'bun', + ['scripts/import-one.ts', spec, OUTDIR], + { stdio: ['ignore', 'inherit', 'inherit'] }, + ); + child.on('exit', (code) => { + if (code !== 0) failed++; + running--; + resolve(); + }); + }); +} + +async function pump(): Promise { + while (queue.length > 0 && running < CONCURRENCY) { + running++; + void runWorker(queue.shift()!).then(() => pump()); + } +} + +await pump(); + +// wait for any stragglers +while (running > 0) { + await new Promise((r) => setTimeout(r, 100)); +} + +const wall = ((Date.now() - started) / 1000).toFixed(1); +console.error(`${imports.length - failed}/${imports.length} imports OK in ${wall}s`); +if (failed) process.exit(1); + +// --- cache the generated output --- +mkdirSync(cachedImports, { recursive: true }); +cpSync(OUTDIR, cachedImports, { recursive: true }); +console.error(`cached -> ${cachedImports}`); diff --git a/scripts/lib/fetch-download.ts b/scripts/lib/fetch-download.ts new file mode 100644 index 0000000..f1eb379 --- /dev/null +++ b/scripts/lib/fetch-download.ts @@ -0,0 +1,43 @@ +/** + * Shared: patch cdk8s-cli's `download()` to use `fetch` + a content cache. + * + * cdk8s-cli's own `download()` (src/util.ts) never drains 301/302 redirect + * bodies, so a redirecting URL holds a socket open and the process hangs ~30s. + * `fetch` follows and drains redirects correctly. The cache avoids re-fetching + * unchanged sources (k8s schema + CRD URLs). + */ +import { createHash } from 'node:crypto'; +import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'node:fs'; +import { join } from 'node:path'; +import { homedir } from 'node:os'; + +const CACHE_DIR = 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: NodeRequire): void { + const util = require('../node_modules/cdk8s-cli/lib/util'); + const original = util.download; + util.download = async (url: string): Promise => { + if (!/^https?:/i.test(url)) { + return original(url); // file: / relative paths -> passthrough + } + const key = createHash('sha256').update(url).digest('hex'); + const cacheFile = join(CACHE_DIR, key); + if (existsSync(cacheFile)) { + return readFileSync(cacheFile, 'utf-8'); + } + const res = await fetch(url, { redirect: 'follow' }); + if (!res.ok) throw new Error(`${res.status} ${res.statusText}: ${url}`); + const text = await res.text(); + mkdirSync(CACHE_DIR, { recursive: true }); + writeFileSync(cacheFile, text); + return text; + }; +} + +export function sourceCacheDir(): string { + return CACHE_DIR; +}