cdk8s-local 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ # cdk8s-local
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 0fff51b: Initial release.
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [0fff51b]
12
+ - cdk8s-kbld2@0.1.0
package/README.md ADDED
@@ -0,0 +1,82 @@
1
+ # cdk8s-local
2
+
3
+ **TLDR: A function to wrap your cdk8s app with a CLI interface that enables quick deploying to a local k3d cluster.**
4
+
5
+ Whenever I use cdk8s, I find myself writing a simple CLI tool that synthesises my app, builds the container images and deploys it to a local k3d cluster, so I can test my changes quickly. So this package provides a function to do just that, with minimal boilerplate.
6
+
7
+ In addition, the CLI wrapper includes support for [`cdk8s-kbld2`](../cdk8s-kbld2/README.md) out of the box, so you can easily run your manifests through `kbld` after synth automatically.
8
+
9
+ ```sh
10
+ # Note: only bun is currently supported as I use `bun` to run shell commands (`kbld`, `kapp`, `k3d`, etc) internally.
11
+ bun i cdk8s-local
12
+ ```
13
+
14
+ ```ts
15
+ import { App } from "cdk8s";
16
+ import { cdk8sLocal } from "cdk8s-local";
17
+ import { option, optional, string } from "cmd-ts";
18
+ import { YourConstruct } from "./your-code";
19
+
20
+ await cdk8sLocal({
21
+ args: {
22
+ someCustomArg: option({
23
+ type: optional(string),
24
+ long: "some-custom-arg",
25
+ short: "a",
26
+ description: "a custom argument you can use for whatever you want, i usually use one to select a config file.",
27
+ }),
28
+ },
29
+ async synth({ args: { someCustomArg }, registry }) {
30
+ // In this function, construct your app as usual, and return the final app instance.
31
+ const app = new App();
32
+
33
+ new YourConstruct(app, {
34
+ someCustomArg,
35
+ registry,
36
+ });
37
+
38
+ return app;
39
+ },
40
+ k3d: {
41
+ // k3d cluster configuration can be customised here:
42
+ servers: 3,
43
+ ports: [
44
+ {
45
+ containerPort: 80,
46
+ hostPort: 8080,
47
+ },
48
+ {
49
+ containerPort: 443,
50
+ hostPort: 8443,
51
+ },
52
+ ],
53
+ // Some convenience options I find useful have been added:
54
+ configureCilium: true,
55
+ disableTraefik: true,
56
+ },
57
+ });
58
+ ```
59
+
60
+ You can then deploy your app locally with:
61
+
62
+ ```
63
+ bun local.ts --some-custom-arg value
64
+ ```
65
+
66
+ > The wrapper depends on some CLI tools being available on your PATH, primarily `k3d`, `kapp`, `kbld` (even if you don't use `cdk8s-kbld2` or the image build step, sorry), `kubectl` and `docker`. The wrapper will check for these and throw an error if they are not found.
67
+
68
+ ## cdk8s-kbld2 Integration
69
+
70
+ If a `KbldConfig` construct is detected in your app, `kbld` will be automatically run against your app's generated manifests, and the resulting manifests will be dumped to stdout.
71
+
72
+ ## Local Registry
73
+
74
+ By default, a local registry will be created by k3d for you to push your images to. This registry is passed to your `synth` function so you can push images to it (e.g. with `cdk8s-kbld2` - or `cdk8s-image` if you really want to).
75
+
76
+ > Security note: This package uses a subdomain of `k3d.hamishwhc.com`, which resolves to localhost, as the name of the local registry. There _is_ potential here for me to swap the DNS record out from under you in a DNS rebinding attack - if you are concerned, you can set up your own DNS entry that resolves to localhost and pass that in via `k3d.registryName`. See [the related k3d documentation](https://k3d.io/v5.1.0/usage/registries/#preface-referencing-local-registries) - this is a method of achieving option 2 here, without needing to modify `/etc/hosts`.
77
+
78
+ ## Configuration
79
+
80
+ All configuration options include documentation within the property descriptions, so you can hover over them in your IDE to see what they do.
81
+
82
+ Some convenience options have been added to the `k3d` configuration to make local development easier. The most notable is `configureCilium`, which sets up k3d as compatible with Cilium by disabling the default CNI and network policies, and then running some ✨ magic ✨ `docker` commands (note that magic here means that I do not understand what the f\*ck they are doing but they do seem to work for me). This option may be rather brittle, so if you run into issues please open an issue or PR!
@@ -0,0 +1,92 @@
1
+ import { DefaultArgs } from "./default-args.mjs";
2
+ import { K3dConfig } from "./k3d-config/index.mjs";
3
+ import { RequiredProgram } from "./requirements.mjs";
4
+ import { ArgTypes, Output } from "@repo/utils/cmd-ts-types";
5
+ import { Awaitable } from "@repo/utils/awaitable";
6
+ import { CommonContext, CommonStartupContext } from "@repo/utils/cli-contexts";
7
+ import { App } from "cdk8s";
8
+
9
+ //#region src/config.d.ts
10
+ interface StartupContext<Args> extends CommonStartupContext<Args, "local"> {}
11
+ interface Context<Args, Data> extends CommonContext<Args, Data, "local"> {}
12
+ interface SynthContext<Args, Data> extends Context<Args, Data> {
13
+ /**
14
+ * Indicates whether the k3d cluster was created during this run.
15
+ */
16
+ create: boolean;
17
+ /**
18
+ * Information about the k3d local registry.
19
+ */
20
+ registry: {
21
+ name: string;
22
+ port: number;
23
+ } | null;
24
+ }
25
+ interface Config<Arguments extends ArgTypes, Data, ParentArguments extends ArgTypes = {}> {
26
+ /**
27
+ * `cmd-ts` argument definitions for the CLI. This will be merged with the default arguments provided by `cdk8s-local`,
28
+ * and passed to your hooks and synth function.
29
+ *
30
+ * You can use this to add your own CLI arguments to allow users to configure your CDK8s app generation.
31
+ */
32
+ args?: Arguments;
33
+ /**
34
+ * A function which synthesizes your CDK8s app. Is expected to return an `App` instance.
35
+ */
36
+ synth: (ctx: SynthContext<Output<Arguments & ParentArguments & DefaultArgs>, Data>) => Awaitable<App>;
37
+ /**
38
+ * Optional configuration for the generated `cmd-ts` command.
39
+ */
40
+ command?: {
41
+ /**
42
+ * @default "local"
43
+ */
44
+ name?: string;
45
+ /**
46
+ * @default "Synthesises, builds and deploys your CDK8s app to a local k3d cluster."
47
+ */
48
+ description?: string;
49
+ };
50
+ /**
51
+ * The name of the k3d cluster to create/use.
52
+ *
53
+ * This is used during cluster creation and when checking for an existing cluster,
54
+ * so it is specified separately from the other k3d configuration.
55
+ * You can still dynamically generate it if you want.
56
+ *
57
+ * @default "cdk8s-local"
58
+ */
59
+ clusterName?: string | ((ctx: Context<Output<Arguments & ParentArguments & DefaultArgs>, Data>) => Awaitable<string>);
60
+ /**
61
+ * k3d configuration.
62
+ */
63
+ k3d?: K3dConfig | ((ctx: Context<Output<Arguments & ParentArguments & DefaultArgs>, Data>) => Awaitable<K3dConfig>);
64
+ /**
65
+ * Programs used under the hood by your CDK8s app which must be installed for the CLI to work.
66
+ * This is in addition to the default required programs used by `cdk8s-local`.
67
+ *
68
+ * e.g. If you are using `Helm` constructs in your app, you should add `helm` here.
69
+ */
70
+ requirements?: RequiredProgram[] | ((ctx: StartupContext<Output<Arguments & ParentArguments & DefaultArgs>>) => Awaitable<RequiredProgram[]>);
71
+ /**
72
+ * These hooks run at various points and allow you to customize the CLI's behavior.
73
+ */
74
+ hooks?: {
75
+ /**
76
+ * A function that will run immediately on startup, but with access to the parsed CLI arguments.
77
+ *
78
+ * You can return custom data that will be passed to subsequent hooks and the synth function.
79
+ */
80
+ startup?: (ctx: StartupContext<Output<Arguments & ParentArguments & DefaultArgs>>) => Awaitable<Data | void>;
81
+ /**
82
+ * A function that will run just before synthesis. If you are using `cdk8s-local`, you probably don't need this,
83
+ * but you may want to use it with `cdk8s-opinionated-cli`, so you can use a common synth function.
84
+ *
85
+ * You can return custom data that will be passed to subsequent hooks and the synth function.
86
+ */
87
+ preSynth?: (ctx: SynthContext<Output<Arguments & ParentArguments & DefaultArgs>, Data>) => Awaitable<Data | void>;
88
+ };
89
+ }
90
+ //#endregion
91
+ export { Config };
92
+ //# sourceMappingURL=config.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.mts","names":[],"sources":["../src/config.ts"],"mappings":";;;;;;;;;UAQiB,cAAA,eAA6B,oBAAA,CAAqB,IAAA;AAAA,UAClD,OAAA,qBAA4B,aAAA,CAAc,IAAA,EAAM,IAAA;AAAA,UAEhD,YAAA,qBAAiC,OAAA,CAAQ,IAAA,EAAM,IAAA;EAHE;;;EAOjE,MAAA;EAPsE;;AACvE;EAUC,QAAA;IACC,IAAA;IACA,IAAA;EAAA;AAAA;AAAA,UAIe,MAAA,mBAAyB,QAAA,gCAAwC,QAAA;EAhBxB;;;;;;EAuBzD,IAAA,GAAO,SAAA;EAvB6D;AAErE;;EAyBC,KAAA,GAAQ,GAAA,EAAK,YAAA,CAAa,MAAA,CAAO,SAAA,GAAY,eAAA,GAAkB,WAAA,GAAc,IAAA,MAAU,SAAA,CAAU,GAAA;EAzBxC;;;EA8BzD,OAAA;IA9BwD;;;IAkCvD,IAAA;IAlCwD;;;IAsCxD,WAAA;EAAA;EA5BA;;;AAIF;;;;;;EAoCC,WAAA,cAAyB,GAAA,EAAK,OAAA,CAAQ,MAAA,CAAO,SAAA,GAAY,eAAA,GAAkB,WAAA,GAAc,IAAA,MAAU,SAAA;EAzBtD;;;EA6B7C,GAAA,GAAM,SAAA,KAAc,GAAA,EAAK,OAAA,CAAQ,MAAA,CAAO,SAAA,GAAY,eAAA,GAAkB,WAAA,GAAc,IAAA,MAAU,SAAA,CAAU,SAAA;EA7B3F;;;;;;EAqCb,YAAA,GACG,eAAA,OACE,GAAA,EAAK,cAAA,CAAe,MAAA,CAAO,SAAA,GAAY,eAAA,GAAkB,WAAA,OAAkB,SAAA,CAAU,eAAA;EAdD;;;EAmBzF,KAAA;IAfwC;;;;;IAqBvC,OAAA,IAAW,GAAA,EAAK,cAAA,CAAe,MAAA,CAAO,SAAA,GAAY,eAAA,GAAkB,WAAA,OAAkB,SAAA,CAAU,IAAA;IArBO;;;;;;IA4BvG,QAAA,IAAY,GAAA,EAAK,YAAA,CAAa,MAAA,CAAO,SAAA,GAAY,eAAA,GAAkB,WAAA,GAAc,IAAA,MAAU,SAAA,CAAU,IAAA;EAAA;AAAA"}
@@ -0,0 +1,19 @@
1
+ import * as cmd_ts_dist_cjs_argparser0 from "cmd-ts/dist/cjs/argparser";
2
+ import * as cmd_ts_dist_cjs_helpdoc0 from "cmd-ts/dist/cjs/helpdoc";
3
+
4
+ //#region src/default-args.d.ts
5
+ declare const defaultArgs: {
6
+ recreate: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
7
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
8
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
9
+ synth: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
10
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
11
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
12
+ build: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
13
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
14
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
15
+ };
16
+ type DefaultArgs = typeof defaultArgs;
17
+ //#endregion
18
+ export { DefaultArgs };
19
+ //# sourceMappingURL=default-args.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-args.d.mts","names":[],"sources":["../src/default-args.ts"],"mappings":";;;;cAEa,WAAA;oBAmBZ,0BAAA,CAAA,QAAA;;;;;;;;;;KACW,WAAA,UAAqB,WAAA"}
@@ -0,0 +1,27 @@
1
+ import { boolean, flag } from "cmd-ts";
2
+
3
+ //#region src/default-args.ts
4
+ const defaultArgs = {
5
+ recreate: flag({
6
+ type: boolean,
7
+ short: "r",
8
+ long: "recreate",
9
+ description: "delete and recreate the local cluster"
10
+ }),
11
+ synth: flag({
12
+ type: boolean,
13
+ short: "s",
14
+ long: "synth",
15
+ description: "only run the synth step, skip build and deploy steps (will skip cluster creation)"
16
+ }),
17
+ build: flag({
18
+ type: boolean,
19
+ short: "b",
20
+ long: "build",
21
+ description: "only run the synth and build (and push) steps, skip deploy step (will NOT skip cluster creation)"
22
+ })
23
+ };
24
+
25
+ //#endregion
26
+ export { defaultArgs };
27
+ //# sourceMappingURL=default-args.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"default-args.mjs","names":[],"sources":["../src/default-args.ts"],"sourcesContent":["import { boolean, flag } from \"cmd-ts\";\n\nexport const defaultArgs = {\n\trecreate: flag({\n\t\ttype: boolean,\n\t\tshort: \"r\",\n\t\tlong: \"recreate\",\n\t\tdescription: \"delete and recreate the local cluster\",\n\t}),\n\tsynth: flag({\n\t\ttype: boolean,\n\t\tshort: \"s\",\n\t\tlong: \"synth\",\n\t\tdescription: \"only run the synth step, skip build and deploy steps (will skip cluster creation)\",\n\t}),\n\tbuild: flag({\n\t\ttype: boolean,\n\t\tshort: \"b\",\n\t\tlong: \"build\",\n\t\tdescription: \"only run the synth and build (and push) steps, skip deploy step (will NOT skip cluster creation)\",\n\t}),\n};\nexport type DefaultArgs = typeof defaultArgs;\n"],"mappings":";;;AAEA,MAAa,cAAc;CAC1B,UAAU,KAAK;EACd,MAAM;EACN,OAAO;EACP,MAAM;EACN,aAAa;EACb,CAAC;CACF,OAAO,KAAK;EACX,MAAM;EACN,OAAO;EACP,MAAM;EACN,aAAa;EACb,CAAC;CACF,OAAO,KAAK;EACX,MAAM;EACN,OAAO;EACP,MAAM;EACN,aAAa;EACb,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ import { $ } from "bun";
2
+
3
+ //#region src/get-k3d-nodes.ts
4
+ async function getK3dNodes(clusterName) {
5
+ return await $`k3d node list -o json`.json();
6
+ }
7
+
8
+ //#endregion
9
+ export { getK3dNodes };
10
+ //# sourceMappingURL=get-k3d-nodes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-k3d-nodes.mjs","names":[],"sources":["../src/get-k3d-nodes.ts"],"sourcesContent":["import { $ } from \"bun\";\n\nexport interface HostBinding {\n\tHostIp: string;\n\tHostPort: string;\n}\n\nexport interface K3dNode {\n\tname: string;\n\trole: \"server\" | \"agent\" | \"loadbalancer\" | \"registry\";\n\tportMappings: Record<string, HostBinding[]>;\n}\n\nexport async function getK3dNodes(clusterName: string): Promise<K3dNode[]> {\n\tconst nodes: K3dNode[] = await $`k3d node list -o json`.json();\n\treturn nodes;\n}\n"],"mappings":";;;AAaA,eAAsB,YAAY,aAAyC;AAE1E,QADyB,MAAM,CAAC,wBAAwB,MAAM"}
@@ -0,0 +1,8 @@
1
+ //#region src/get-registry.d.ts
2
+ interface RegistryInfo {
3
+ name: string;
4
+ port: number;
5
+ }
6
+ //#endregion
7
+ export { RegistryInfo };
8
+ //# sourceMappingURL=get-registry.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-registry.d.mts","names":[],"sources":["../src/get-registry.ts"],"mappings":";UAEiB,YAAA;EAChB,IAAA;EACA,IAAA;AAAA"}
@@ -0,0 +1,17 @@
1
+ import { getK3dNodes } from "./get-k3d-nodes.mjs";
2
+
3
+ //#region src/get-registry.ts
4
+ async function getRegistry(clusterName) {
5
+ const registryNode = (await getK3dNodes(clusterName)).find((node) => node.role === "registry");
6
+ if (!registryNode) return null;
7
+ const registryBindings = registryNode.portMappings["5000/tcp"];
8
+ if (!registryBindings || registryBindings.length === 0) return null;
9
+ return {
10
+ name: registryNode.name,
11
+ port: parseInt(registryBindings[0].HostPort)
12
+ };
13
+ }
14
+
15
+ //#endregion
16
+ export { getRegistry };
17
+ //# sourceMappingURL=get-registry.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"get-registry.mjs","names":[],"sources":["../src/get-registry.ts"],"sourcesContent":["import { getK3dNodes } from \"./get-k3d-nodes\";\n\nexport interface RegistryInfo {\n\tname: string;\n\tport: number;\n}\n\nexport async function getRegistry(clusterName: string): Promise<RegistryInfo | null> {\n\tconst nodes = await getK3dNodes(clusterName);\n\tconst registryNode = nodes.find((node) => node.role === \"registry\");\n\tif (!registryNode) {\n\t\treturn null;\n\t}\n\n\tconst registryBindings = registryNode.portMappings[\"5000/tcp\"];\n\tif (!registryBindings || registryBindings.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tname: registryNode.name,\n\t\tport: parseInt(registryBindings[0]!.HostPort),\n\t};\n}\n"],"mappings":";;;AAOA,eAAsB,YAAY,aAAmD;CAEpF,MAAM,gBADQ,MAAM,YAAY,YAAY,EACjB,MAAM,SAAS,KAAK,SAAS,WAAW;AACnE,KAAI,CAAC,aACJ,QAAO;CAGR,MAAM,mBAAmB,aAAa,aAAa;AACnD,KAAI,CAAC,oBAAoB,iBAAiB,WAAW,EACpD,QAAO;AAGR,QAAO;EACN,MAAM,aAAa;EACnB,MAAM,SAAS,iBAAiB,GAAI,SAAS;EAC7C"}
@@ -0,0 +1,53 @@
1
+ import { K3DSimpleConfigV1Alpha5 } from "./k3d-config/v1alpha5.mjs";
2
+ import { BindMountVolume, EnvironmentVariable, K3dConfig, K3dConfigResolution, K3dSimpleConfig, K3sArg, Port } from "./k3d-config/index.mjs";
3
+ import { CommonRequirements } from "./requirements.mjs";
4
+ import { Config } from "./config.mjs";
5
+ import isWsl from "is-wsl";
6
+ import * as cmd_ts_dist_cjs_argparser0 from "cmd-ts/dist/cjs/argparser";
7
+ import * as cmd_ts_dist_cjs_helpdoc0 from "cmd-ts/dist/cjs/helpdoc";
8
+ import * as cmd_ts_dist_cjs_runner0 from "cmd-ts/dist/cjs/runner";
9
+ import { ArgTypes } from "@repo/utils/cmd-ts-types";
10
+
11
+ //#region src/index.d.ts
12
+ /**
13
+ * Creates a cdk8s-local CLI command based on the provided configuration.
14
+ *
15
+ * Unless you want to integrate a cdk8s-local command into an existing cmd-ts CLI, you probably want to use `cdk8sLocal` instead.
16
+ */
17
+ declare function cdk8sLocalCommand<Arguments extends ArgTypes, Data>(config: Config<Arguments, Data>): Partial<cmd_ts_dist_cjs_argparser0.Register> & {
18
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<Arguments & {
19
+ recreate: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
20
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
21
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
22
+ synth: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
23
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
24
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
25
+ build: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
26
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
27
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
28
+ } extends infer T extends {
29
+ [x: string]: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
30
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<any>>;
31
+ } & Partial<cmd_ts_dist_cjs_helpdoc0.ProvidesHelp>;
32
+ } ? { [key in keyof T]: cmd_ts_dist_cjs_argparser0.ParsingInto<T[key]> } : never>>;
33
+ } & cmd_ts_dist_cjs_helpdoc0.PrintHelp & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_helpdoc0.Named & Partial<cmd_ts_dist_cjs_helpdoc0.Versioned> & cmd_ts_dist_cjs_argparser0.Register & cmd_ts_dist_cjs_runner0.Handling<Arguments & {
34
+ recreate: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
35
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
36
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
37
+ synth: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
38
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
39
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
40
+ build: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
41
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<boolean>>;
42
+ } & cmd_ts_dist_cjs_helpdoc0.ProvidesHelp & cmd_ts_dist_cjs_argparser0.Register & Partial<cmd_ts_dist_cjs_helpdoc0.Descriptive>;
43
+ } extends infer T extends {
44
+ [x: string]: Partial<cmd_ts_dist_cjs_argparser0.Register> & {
45
+ parse(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<any>>;
46
+ } & Partial<cmd_ts_dist_cjs_helpdoc0.ProvidesHelp>;
47
+ } ? { [key in keyof T]: cmd_ts_dist_cjs_argparser0.ParsingInto<T[key]> } : never, Promise<1 | undefined>> & {
48
+ run(context: cmd_ts_dist_cjs_argparser0.ParseContext): Promise<cmd_ts_dist_cjs_argparser0.ParsingResult<Promise<1 | undefined>>>;
49
+ } & Partial<cmd_ts_dist_cjs_helpdoc0.Versioned & cmd_ts_dist_cjs_helpdoc0.Descriptive & cmd_ts_dist_cjs_helpdoc0.Aliased>;
50
+ declare function cdk8sLocal<Arguments extends ArgTypes, Data>(config: Config<Arguments, Data>): Promise<void>;
51
+ //#endregion
52
+ export { type BindMountVolume, CommonRequirements, Config, type EnvironmentVariable, type K3DSimpleConfigV1Alpha5, type K3dConfig, type K3dConfigResolution, type K3dSimpleConfig, type K3sArg, type Port, cdk8sLocal, cdk8sLocalCommand, isWsl };
53
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;;;;;;;;;;;iBAoCgB,iBAAA,mBAAoC,QAAA,OAAA,CAAgB,MAAA,EAAQ,MAAA,CAAO,SAAA,EAAW,IAAA,IAAK,OAAA,CAAjB,0BAAA,CAAiB,QAAA;iBAAA,0BAAA,CAAA,YAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAiK7E,UAAA,mBAA6B,QAAA,OAAA,CAAgB,MAAA,EAAQ,MAAA,CAAO,SAAA,EAAW,IAAA,IAAK,OAAA"}
package/dist/index.mjs ADDED
@@ -0,0 +1,153 @@
1
+ import { defaultArgs } from "./default-args.mjs";
2
+ import { getK3dNodes } from "./get-k3d-nodes.mjs";
3
+ import { getRegistry } from "./get-registry.mjs";
4
+ import { resolveK3dConfig } from "./k3d-config/index.mjs";
5
+ import { DEFAULT_REQUIREMENTS, checkRequirements } from "./requirements.mjs";
6
+ import { logger } from "@repo/utils/logger";
7
+ import { resolveThunk } from "@repo/utils/thunk";
8
+ import { $ } from "bun";
9
+ import { KbldConfig } from "cdk8s-kbld2";
10
+ import { binary, command, run } from "cmd-ts";
11
+ import { ConstructOrder } from "constructs";
12
+ import { rm } from "fs/promises";
13
+ import isWsl from "is-wsl";
14
+ import path from "path";
15
+
16
+ //#region src/index.ts
17
+ /**
18
+ * Creates a cdk8s-local CLI command based on the provided configuration.
19
+ *
20
+ * Unless you want to integrate a cdk8s-local command into an existing cmd-ts CLI, you probably want to use `cdk8sLocal` instead.
21
+ */
22
+ function cdk8sLocalCommand(config) {
23
+ return command({
24
+ name: config.command?.name ?? "local",
25
+ description: config.command?.description ?? "Synthesises, builds and deploys your CDK8s app to a local k3d cluster.",
26
+ args: {
27
+ ...config.args,
28
+ ...defaultArgs
29
+ },
30
+ handler: async (args) => {
31
+ const startupCtx = {
32
+ args,
33
+ command: "local"
34
+ };
35
+ const additionalRequirements = await resolveThunk(config.requirements, startupCtx) ?? [];
36
+ await checkRequirements([...DEFAULT_REQUIREMENTS, ...additionalRequirements]);
37
+ const ctx = {
38
+ ...startupCtx,
39
+ data: await config.hooks?.startup?.(startupCtx) ?? {}
40
+ };
41
+ const clusterName = await resolveThunk(config.clusterName, ctx) ?? "cdk8s-local";
42
+ const clusterGetProc = await $`k3d cluster get ${clusterName}`.throws(false).quiet();
43
+ let create = true;
44
+ if (clusterGetProc.exitCode === 0) if (args.recreate) {
45
+ logger.info("Deleting existing k3d cluster...");
46
+ await $`k3d cluster delete ${clusterName}`.quiet();
47
+ } else create = false;
48
+ let registry = create ? null : await getRegistry(clusterName);
49
+ let k3dConfigResolution = null;
50
+ if (create) {
51
+ k3dConfigResolution = await resolveK3dConfig(clusterName, await resolveThunk(config.k3d, ctx));
52
+ registry = k3dConfigResolution.registry;
53
+ }
54
+ const synthCtx = {
55
+ ...ctx,
56
+ create,
57
+ registry
58
+ };
59
+ synthCtx.data = await config.hooks?.preSynth?.(synthCtx) ?? synthCtx.data;
60
+ logger.info("Running synth (generating manifests)...");
61
+ const app = await config.synth(synthCtx);
62
+ await rm(app.outdir, {
63
+ recursive: true,
64
+ force: true
65
+ });
66
+ app.synth();
67
+ if (args.synth) {
68
+ logger.info("Synth complete, skipping build and deploy, find manifests in " + path.relative(process.cwd(), app.outdir));
69
+ return;
70
+ }
71
+ if (k3dConfigResolution) {
72
+ logger.info(`Creating k3d cluster (${clusterName})...`);
73
+ if (k3dConfigResolution.originalConfig?.configureCilium && isWsl) {
74
+ logger.warn("Cilium configuration is not supported on WSL, skipping Cilium setup for k3d.");
75
+ k3dConfigResolution.originalConfig.configureCilium = false;
76
+ }
77
+ for (const volume of k3dConfigResolution.originalConfig?.volumes ?? []) {
78
+ if (volume.deleteOnClusterCreate) await rm(volume.hostPath, {
79
+ recursive: true,
80
+ force: true
81
+ });
82
+ await $`mkdir -p ${volume.hostPath}`;
83
+ }
84
+ await $`k3d cluster create -c - < ${Buffer.from(k3dConfigResolution.resolvedConfig, "utf-8")}`;
85
+ if (k3dConfigResolution.originalConfig?.configureCilium) {
86
+ const nodes = await getK3dNodes(clusterName);
87
+ for (const { name, role } of nodes) {
88
+ if (role !== "server" && role !== "agent") continue;
89
+ await $`docker exec -it ${name} mount bpffs /sys/fs/bpf -t bpf`;
90
+ await $`docker exec -it ${name} mount --make-shared /sys/fs/bpf`;
91
+ await $`docker exec -it ${name} mkdir -p /run/cilium/cgroupv2`;
92
+ await $`docker exec -it ${name} mount -t cgroup2 none /run/cilium/cgroupv2`;
93
+ await $`docker exec -it ${name} mount --make-shared /run/cilium/cgroupv2`;
94
+ await $`docker exec -it ${name} mount --make-rshared /var/run`;
95
+ }
96
+ }
97
+ }
98
+ let manifestSource = {
99
+ type: "directory",
100
+ path: app.outdir
101
+ };
102
+ if (app.node.findAll(ConstructOrder.POSTORDER).find((c) => c instanceof KbldConfig)) {
103
+ logger.info("Detected kbld config construct, running kbld...");
104
+ try {
105
+ manifestSource = {
106
+ type: "buffer",
107
+ buffer: await $`kbld -f ${app.outdir}`.arrayBuffer()
108
+ };
109
+ } catch (e) {
110
+ if (e instanceof $.ShellError) {
111
+ logger.error(e.stderr.toString("utf-8"));
112
+ logger.fatal("Failed to build images from manifests.");
113
+ return 1;
114
+ }
115
+ throw e;
116
+ }
117
+ } else if (args.build) logger.warn("The --build flag was set but no kbld config was found in the app. Skipping build step - no manifests will be emitted on stdout.");
118
+ if (args.build) {
119
+ if (manifestSource.type === "buffer") process.stdout.write(Buffer.from(manifestSource.buffer));
120
+ return;
121
+ }
122
+ logger.info("Applying manifests to local cluster...");
123
+ const context = (await $`kubectl config current-context`.text()).trim();
124
+ if (context !== `k3d-${clusterName}`) {
125
+ logger.fatal(`Will not deploy local environment to context: ${context}`);
126
+ return 1;
127
+ }
128
+ try {
129
+ const commonArguments = [
130
+ "-y",
131
+ "-a",
132
+ clusterName
133
+ ];
134
+ if (manifestSource.type === "directory") await $`kapp deploy ${commonArguments} -f ${manifestSource.path}`;
135
+ else await $`kapp deploy ${commonArguments} -f - < ${manifestSource.buffer}`;
136
+ } catch (e) {
137
+ if (e instanceof $.ShellError) {
138
+ logger.fatal("Failed to deploy manifests to local cluster.");
139
+ return 1;
140
+ }
141
+ throw e;
142
+ }
143
+ logger.info("Local deployment successful.");
144
+ }
145
+ });
146
+ }
147
+ async function cdk8sLocal(config) {
148
+ await run(binary(cdk8sLocalCommand(config)), process.argv);
149
+ }
150
+
151
+ //#endregion
152
+ export { cdk8sLocal, cdk8sLocalCommand, isWsl };
153
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import type { ArgTypes } from \"@repo/utils/cmd-ts-types\";\nimport { logger } from \"@repo/utils/logger\";\nimport { resolveThunk } from \"@repo/utils/thunk\";\nimport { $ } from \"bun\";\nimport { KbldConfig } from \"cdk8s-kbld2\";\nimport { binary, command, run } from \"cmd-ts\";\nimport { ConstructOrder } from \"constructs\";\nimport { rm } from \"fs/promises\";\nimport isWsl from \"is-wsl\";\nimport path from \"path\";\nimport type { Config } from \"./config\";\nimport { defaultArgs } from \"./default-args\";\nimport { getK3dNodes } from \"./get-k3d-nodes\";\nimport { getRegistry } from \"./get-registry\";\nimport { resolveK3dConfig, type K3dConfigResolution } from \"./k3d-config\";\nimport { checkRequirements, DEFAULT_REQUIREMENTS, type CommonRequirements } from \"./requirements\";\n\ntype ManifestSource = { type: \"directory\"; path: string } | { type: \"buffer\"; buffer: ArrayBuffer };\n\nexport type {\n\tBindMountVolume,\n\tEnvironmentVariable,\n\tK3dConfig,\n\tK3dConfigResolution,\n\tK3dSimpleConfig,\n\tK3DSimpleConfigV1Alpha5,\n\tK3sArg,\n\tPort,\n} from \"./k3d-config\";\nexport { CommonRequirements, Config, isWsl };\n\n/**\n * Creates a cdk8s-local CLI command based on the provided configuration.\n *\n * Unless you want to integrate a cdk8s-local command into an existing cmd-ts CLI, you probably want to use `cdk8sLocal` instead.\n */\nexport function cdk8sLocalCommand<Arguments extends ArgTypes, Data>(config: Config<Arguments, Data>) {\n\treturn command({\n\t\tname: config.command?.name ?? \"local\",\n\t\tdescription:\n\t\t\tconfig.command?.description ?? \"Synthesises, builds and deploys your CDK8s app to a local k3d cluster.\",\n\t\targs: {\n\t\t\t...config.args!,\n\t\t\t...defaultArgs,\n\t\t},\n\t\thandler: async (args) => {\n\t\t\tconst startupCtx = { args, command: \"local\" as const };\n\t\t\tconst additionalRequirements = (await resolveThunk(config.requirements, startupCtx)) ?? [];\n\t\t\tawait checkRequirements([...DEFAULT_REQUIREMENTS, ...additionalRequirements]);\n\n\t\t\tconst ctx = {\n\t\t\t\t...startupCtx,\n\t\t\t\tdata: (await config.hooks?.startup?.(startupCtx)) ?? ({} as Data),\n\t\t\t};\n\n\t\t\tconst clusterName = (await resolveThunk(config.clusterName, ctx)) ?? \"cdk8s-local\";\n\n\t\t\tconst clusterGetProc = await $`k3d cluster get ${clusterName}`.throws(false).quiet();\n\t\t\tlet create = true;\n\t\t\tif (clusterGetProc.exitCode === 0) {\n\t\t\t\tif (args.recreate) {\n\t\t\t\t\tlogger.info(\"Deleting existing k3d cluster...\");\n\t\t\t\t\tawait $`k3d cluster delete ${clusterName}`.quiet();\n\t\t\t\t} else {\n\t\t\t\t\tcreate = false;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet registry = create ? null : await getRegistry(clusterName);\n\t\t\tlet k3dConfigResolution: K3dConfigResolution | null = null;\n\n\t\t\tif (create) {\n\t\t\t\tconst k3dConfig = await resolveThunk(config.k3d, ctx);\n\t\t\t\tk3dConfigResolution = await resolveK3dConfig(clusterName, k3dConfig);\n\t\t\t\tregistry = k3dConfigResolution.registry;\n\t\t\t}\n\n\t\t\tconst synthCtx = {\n\t\t\t\t...ctx,\n\t\t\t\tcreate,\n\t\t\t\tregistry,\n\t\t\t};\n\t\t\tsynthCtx.data = (await config.hooks?.preSynth?.(synthCtx)) ?? synthCtx.data;\n\n\t\t\tlogger.info(\"Running synth (generating manifests)...\");\n\t\t\tconst app = await config.synth(synthCtx);\n\t\t\tawait rm(app.outdir, { recursive: true, force: true });\n\t\t\tapp.synth();\n\n\t\t\tif (args.synth) {\n\t\t\t\tlogger.info(\n\t\t\t\t\t\"Synth complete, skipping build and deploy, find manifests in \" + path.relative(process.cwd(), app.outdir),\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (k3dConfigResolution) {\n\t\t\t\tlogger.info(`Creating k3d cluster (${clusterName})...`);\n\t\t\t\tif (k3dConfigResolution.originalConfig?.configureCilium && isWsl) {\n\t\t\t\t\tlogger.warn(\"Cilium configuration is not supported on WSL, skipping Cilium setup for k3d.\");\n\t\t\t\t\tk3dConfigResolution.originalConfig.configureCilium = false;\n\t\t\t\t}\n\n\t\t\t\tfor (const volume of k3dConfigResolution.originalConfig?.volumes ?? []) {\n\t\t\t\t\tif (volume.deleteOnClusterCreate) {\n\t\t\t\t\t\tawait rm(volume.hostPath, { recursive: true, force: true });\n\t\t\t\t\t}\n\t\t\t\t\tawait $`mkdir -p ${volume.hostPath}`;\n\t\t\t\t}\n\n\t\t\t\tconst configBuffer = Buffer.from(k3dConfigResolution.resolvedConfig, \"utf-8\");\n\t\t\t\tawait $`k3d cluster create -c - < ${configBuffer}`;\n\n\t\t\t\tif (k3dConfigResolution.originalConfig?.configureCilium) {\n\t\t\t\t\tconst nodes = await getK3dNodes(clusterName);\n\t\t\t\t\tfor (const { name, role } of nodes) {\n\t\t\t\t\t\tif (role !== \"server\" && role !== \"agent\") {\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Magic to make Cilium work on MacOS (and hopefully Linux) Docker Desktop/OrbStack:\n\t\t\t\t\t\t// - https://sandstorm.de/blog/posts/running-cilium-in-k3s-and-k3d-lightweight-kubernetes-on-mac-os-for-development\n\t\t\t\t\t\t// - https://github.com/cilium/cilium/issues/10516\n\t\t\t\t\t\t// - https://github.com/k3d-io/k3d/issues/363\n\t\t\t\t\t\t// - https://docs.cilium.io/en/v1.14/installation/rancher-desktop/#rancher-desktop-install\n\t\t\t\t\t\t// - https://github.com/istio/istio/issues/54865\n\t\t\t\t\t\tawait $`docker exec -it ${name} mount bpffs /sys/fs/bpf -t bpf`;\n\t\t\t\t\t\tawait $`docker exec -it ${name} mount --make-shared /sys/fs/bpf`;\n\t\t\t\t\t\tawait $`docker exec -it ${name} mkdir -p /run/cilium/cgroupv2`;\n\t\t\t\t\t\tawait $`docker exec -it ${name} mount -t cgroup2 none /run/cilium/cgroupv2`;\n\t\t\t\t\t\tawait $`docker exec -it ${name} mount --make-shared /run/cilium/cgroupv2`;\n\t\t\t\t\t\tawait $`docker exec -it ${name} mount --make-rshared /var/run`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet manifestSource: ManifestSource = { type: \"directory\" as const, path: app.outdir };\n\t\t\tif (app.node.findAll(ConstructOrder.POSTORDER).find((c) => c instanceof KbldConfig)) {\n\t\t\t\tlogger.info(\"Detected kbld config construct, running kbld...\");\n\t\t\t\ttry {\n\t\t\t\t\tmanifestSource = { type: \"buffer\" as const, buffer: await $`kbld -f ${app.outdir}`.arrayBuffer() };\n\t\t\t\t} catch (e) {\n\t\t\t\t\tif (e instanceof $.ShellError) {\n\t\t\t\t\t\tlogger.error(e.stderr.toString(\"utf-8\"));\n\t\t\t\t\t\tlogger.fatal(\"Failed to build images from manifests.\");\n\t\t\t\t\t\treturn 1;\n\t\t\t\t\t}\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t} else if (args.build) {\n\t\t\t\tlogger.warn(\n\t\t\t\t\t\"The --build flag was set but no kbld config was found in the app. Skipping build step - no manifests will be emitted on stdout.\",\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (args.build) {\n\t\t\t\tif (manifestSource.type === \"buffer\") {\n\t\t\t\t\tprocess.stdout.write(Buffer.from(manifestSource.buffer));\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlogger.info(\"Applying manifests to local cluster...\");\n\n\t\t\t// TODO: Set KUBECONFIG for kapp to use the k3d cluster config directly, rather than relying on k3d setting current-context.\n\t\t\tconst context = (await $`kubectl config current-context`.text()).trim();\n\t\t\tif (context !== `k3d-${clusterName}`) {\n\t\t\t\tlogger.fatal(`Will not deploy local environment to context: ${context}`);\n\t\t\t\t// throw new CliError(\"Current kubectl context does not match k3d cluster context.\");\n\t\t\t\treturn 1;\n\t\t\t}\n\n\t\t\t// TODO: Re-evaluate if this is needed in general - it was originally added as I used to only include CRDs when creating a cluster.\n\t\t\t// const filterJson = {\n\t\t\t// \tnot: { and: [{ ops: [\"delete\"] }, { existingResource: { kinds: [\"CustomResourceDefinition\"] } }] },\n\t\t\t// };\n\t\t\t// const filter = `--diff-filter=${JSON.stringify(filterJson)}`;\n\t\t\ttry {\n\t\t\t\tconst commonArguments = [\"-y\", \"-a\", clusterName];\n\t\t\t\tif (manifestSource.type === \"directory\") {\n\t\t\t\t\tawait $`kapp deploy ${commonArguments} -f ${manifestSource.path}`;\n\t\t\t\t} else {\n\t\t\t\t\tawait $`kapp deploy ${commonArguments} -f - < ${manifestSource.buffer}`;\n\t\t\t\t}\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof $.ShellError) {\n\t\t\t\t\tlogger.fatal(\"Failed to deploy manifests to local cluster.\");\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\t\t\t\tthrow e;\n\t\t\t}\n\n\t\t\tlogger.info(\"Local deployment successful.\");\n\t\t},\n\t});\n}\n\nexport async function cdk8sLocal<Arguments extends ArgTypes, Data>(config: Config<Arguments, Data>) {\n\tconst cmd = cdk8sLocalCommand(config);\n\tawait run(binary(cmd), process.argv);\n}\n"],"mappings":"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"}