pepr 0.33.0 → 0.34.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.
Files changed (140) hide show
  1. package/README.md +2 -1
  2. package/dist/cli/banner.d.ts +2 -0
  3. package/dist/cli/banner.d.ts.map +1 -0
  4. package/dist/cli/build.d.ts +19 -0
  5. package/dist/cli/build.d.ts.map +1 -0
  6. package/dist/cli/deploy.d.ts +3 -0
  7. package/dist/cli/deploy.d.ts.map +1 -0
  8. package/dist/cli/dev.d.ts +3 -0
  9. package/dist/cli/dev.d.ts.map +1 -0
  10. package/dist/cli/format.d.ts +9 -0
  11. package/dist/cli/format.d.ts.map +1 -0
  12. package/dist/cli/init/index.d.ts +3 -0
  13. package/dist/cli/init/index.d.ts.map +1 -0
  14. package/dist/cli/init/templates.d.ts +196 -0
  15. package/dist/cli/init/templates.d.ts.map +1 -0
  16. package/dist/cli/init/utils.d.ts +21 -0
  17. package/dist/cli/init/utils.d.ts.map +1 -0
  18. package/dist/cli/init/utils.test.d.ts +2 -0
  19. package/dist/cli/init/utils.test.d.ts.map +1 -0
  20. package/dist/cli/init/walkthrough.d.ts +8 -0
  21. package/dist/cli/init/walkthrough.d.ts.map +1 -0
  22. package/dist/cli/init/walkthrough.test.d.ts +2 -0
  23. package/dist/cli/init/walkthrough.test.d.ts.map +1 -0
  24. package/dist/cli/kfc.d.ts +3 -0
  25. package/dist/cli/kfc.d.ts.map +1 -0
  26. package/dist/cli/monitor.d.ts +3 -0
  27. package/dist/cli/monitor.d.ts.map +1 -0
  28. package/dist/cli/root.d.ts +5 -0
  29. package/dist/cli/root.d.ts.map +1 -0
  30. package/dist/cli/update.d.ts +3 -0
  31. package/dist/cli/update.d.ts.map +1 -0
  32. package/dist/cli/uuid.d.ts +3 -0
  33. package/dist/cli/uuid.d.ts.map +1 -0
  34. package/dist/cli.js +68 -38
  35. package/dist/controller.js +1 -2
  36. package/dist/fixtures/loader.d.ts +5 -0
  37. package/dist/fixtures/loader.d.ts.map +1 -0
  38. package/dist/lib/assets/helm.d.ts +1 -0
  39. package/dist/lib/assets/helm.d.ts.map +1 -1
  40. package/dist/lib/assets/helm.test.d.ts +2 -0
  41. package/dist/lib/assets/helm.test.d.ts.map +1 -0
  42. package/dist/lib/assets/index.d.ts.map +1 -1
  43. package/dist/lib/assets/pods.d.ts +3 -0
  44. package/dist/lib/assets/pods.d.ts.map +1 -1
  45. package/dist/lib/assets/pods.test.d.ts +2 -0
  46. package/dist/lib/assets/pods.test.d.ts.map +1 -0
  47. package/dist/lib/assets/yaml.d.ts.map +1 -1
  48. package/dist/lib/errors.test.d.ts +2 -0
  49. package/dist/lib/errors.test.d.ts.map +1 -0
  50. package/dist/lib/filter.test.d.ts +2 -0
  51. package/dist/lib/filter.test.d.ts.map +1 -0
  52. package/dist/lib/helpers.d.ts +0 -5
  53. package/dist/lib/helpers.d.ts.map +1 -1
  54. package/dist/lib/helpers.test.d.ts +2 -0
  55. package/dist/lib/helpers.test.d.ts.map +1 -0
  56. package/dist/lib/included-files.test.d.ts +2 -0
  57. package/dist/lib/included-files.test.d.ts.map +1 -0
  58. package/dist/lib/logger.test.d.ts +2 -0
  59. package/dist/lib/logger.test.d.ts.map +1 -0
  60. package/dist/lib/metrics.d.ts +18 -0
  61. package/dist/lib/metrics.d.ts.map +1 -1
  62. package/dist/lib/metrics.test.d.ts +2 -0
  63. package/dist/lib/metrics.test.d.ts.map +1 -0
  64. package/dist/lib/module.test.d.ts +2 -0
  65. package/dist/lib/module.test.d.ts.map +1 -0
  66. package/dist/lib/mutate-request.test.d.ts +2 -0
  67. package/dist/lib/mutate-request.test.d.ts.map +1 -0
  68. package/dist/lib/queue.test.d.ts +2 -0
  69. package/dist/lib/queue.test.d.ts.map +1 -0
  70. package/dist/lib/schedule.test.d.ts +15 -0
  71. package/dist/lib/schedule.test.d.ts.map +1 -0
  72. package/dist/lib/storage.test.d.ts +2 -0
  73. package/dist/lib/storage.test.d.ts.map +1 -0
  74. package/dist/lib/tls.test.d.ts +2 -0
  75. package/dist/lib/tls.test.d.ts.map +1 -0
  76. package/dist/lib/utils.test.d.ts +2 -0
  77. package/dist/lib/utils.test.d.ts.map +1 -0
  78. package/dist/lib/validate-request.test.d.ts +2 -0
  79. package/dist/lib/validate-request.test.d.ts.map +1 -0
  80. package/dist/lib/watch-processor.d.ts.map +1 -1
  81. package/dist/lib/watch-processor.test.d.ts +2 -0
  82. package/dist/lib/watch-processor.test.d.ts.map +1 -0
  83. package/dist/lib.js +76 -20
  84. package/dist/lib.js.map +3 -3
  85. package/dist/sdk/sdk.test.d.ts +2 -0
  86. package/dist/sdk/sdk.test.d.ts.map +1 -0
  87. package/package.json +21 -15
  88. package/src/cli/banner.ts +63 -0
  89. package/src/cli/build.ts +370 -0
  90. package/src/cli/deploy.ts +105 -0
  91. package/src/cli/dev.ts +118 -0
  92. package/src/cli/format.ts +83 -0
  93. package/src/cli/init/index.ts +99 -0
  94. package/src/cli/init/templates.ts +124 -0
  95. package/src/cli/init/utils.test.ts +28 -0
  96. package/src/cli/init/utils.ts +55 -0
  97. package/src/cli/init/walkthrough.test.ts +21 -0
  98. package/src/cli/init/walkthrough.ts +96 -0
  99. package/src/cli/kfc.ts +45 -0
  100. package/src/cli/monitor.ts +101 -0
  101. package/src/cli/root.ts +12 -0
  102. package/src/cli/update.ts +95 -0
  103. package/src/cli/uuid.ts +44 -0
  104. package/src/fixtures/data/create-pod.json +271 -0
  105. package/src/fixtures/data/delete-pod.json +271 -0
  106. package/src/fixtures/loader.ts +18 -0
  107. package/src/lib/.prettierrc +14 -0
  108. package/src/lib/assets/helm.test.ts +64 -0
  109. package/src/lib/assets/helm.ts +35 -0
  110. package/src/lib/assets/index.ts +5 -1
  111. package/src/lib/assets/pods.test.ts +553 -0
  112. package/src/lib/assets/pods.ts +14 -6
  113. package/src/lib/assets/yaml.ts +15 -15
  114. package/src/lib/controller/index.ts +2 -2
  115. package/src/lib/errors.test.ts +85 -0
  116. package/src/lib/filter.test.ts +384 -0
  117. package/src/lib/helpers.test.ts +1192 -0
  118. package/src/lib/helpers.ts +0 -17
  119. package/src/lib/included-files.test.ts +22 -0
  120. package/src/lib/logger.test.ts +18 -0
  121. package/src/lib/metrics.test.ts +132 -0
  122. package/src/lib/metrics.ts +68 -6
  123. package/src/lib/module.test.ts +126 -0
  124. package/src/lib/mutate-request.test.ts +188 -0
  125. package/src/lib/queue.test.ts +58 -0
  126. package/src/lib/schedule.test.ts +217 -0
  127. package/src/lib/storage.test.ts +203 -0
  128. package/src/lib/tls.test.ts +18 -0
  129. package/src/lib/utils.test.ts +69 -0
  130. package/src/lib/validate-request.test.ts +124 -0
  131. package/src/lib/watch-processor.test.ts +322 -0
  132. package/src/lib/watch-processor.ts +20 -4
  133. package/src/sdk/sdk.test.ts +243 -0
  134. package/src/templates/.eslintrc.json +6 -0
  135. package/.prettierignore +0 -1
  136. package/CODE_OF_CONDUCT.md +0 -133
  137. package/SECURITY.md +0 -18
  138. package/SUPPORT.md +0 -16
  139. package/codecov.yaml +0 -19
  140. package/commitlint.config.js +0 -1
@@ -0,0 +1,370 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { execSync, execFileSync } from "child_process";
5
+ import { BuildOptions, BuildResult, analyzeMetafile, context } from "esbuild";
6
+ import { promises as fs } from "fs";
7
+ import { basename, dirname, extname, resolve } from "path";
8
+ import { createDockerfile } from "../lib/included-files";
9
+ import { Assets } from "../lib/assets";
10
+ import { dependencies, version } from "./init/templates";
11
+ import { RootCmd } from "./root";
12
+ import { peprFormat } from "./format";
13
+ import { Option } from "commander";
14
+ import { createDirectoryIfNotExists, validateCapabilityNames, parseTimeout } from "../lib/helpers";
15
+ import { sanitizeResourceName } from "../sdk/sdk";
16
+
17
+ const peprTS = "pepr.ts";
18
+ let outputDir: string = "dist";
19
+ export type Reloader = (opts: BuildResult<BuildOptions>) => void | Promise<void>;
20
+
21
+ export default function (program: RootCmd) {
22
+ program
23
+ .command("build")
24
+ .description("Build a Pepr Module for deployment")
25
+ .option("-e, --entry-point [file]", "Specify the entry point file to build with.", peprTS)
26
+ .option(
27
+ "-n, --no-embed",
28
+ "Disables embedding of deployment files into output module. Useful when creating library modules intended solely for reuse/distribution via NPM.",
29
+ )
30
+ .option(
31
+ "-i, --custom-image <custom-image>",
32
+ "Custom Image: Use custom image for Admission and Watch Deployments.",
33
+ )
34
+ .option(
35
+ "-r, --registry-info [<registry>/<username>]",
36
+ "Registry Info: Image registry and username. Note: You must be signed into the registry",
37
+ )
38
+ .option("-o, --output-dir <output directory>", "Define where to place build output")
39
+ .option(
40
+ "--timeout <timeout>",
41
+ "How long the API server should wait for a webhook to respond before treating the call as a failure",
42
+ parseTimeout,
43
+ )
44
+ .option(
45
+ "-v, --version <version>. Example: '0.27.3'",
46
+ "The version of the Pepr image to use in the deployment manifests.",
47
+ )
48
+ .option(
49
+ "--withPullSecret <imagePullSecret>",
50
+ "Image Pull Secret: Use image pull secret for controller Deployment.",
51
+ )
52
+
53
+ .addOption(
54
+ new Option(
55
+ "--registry <GitHub|Iron Bank>",
56
+ "Container registry: Choose container registry for deployment manifests. Can't be used with --custom-image.",
57
+ ).choices(["GitHub", "Iron Bank"]),
58
+ )
59
+
60
+ .addOption(
61
+ new Option(
62
+ "-z, --zarf [manifest|chart]",
63
+ "Zarf package type: manifest, chart (default: manifest)",
64
+ )
65
+ .choices(["manifest", "chart"])
66
+ .default("manifest"),
67
+ )
68
+ .addOption(
69
+ new Option("--rbac-mode [admin|scoped]", "Rbac Mode: admin, scoped (default: admin)")
70
+ .choices(["admin", "scoped"])
71
+ .default("admin"),
72
+ )
73
+ .action(async opts => {
74
+ // assign custom output directory if provided
75
+ if (opts.outputDir) {
76
+ outputDir = opts.outputDir;
77
+ createDirectoryIfNotExists(outputDir).catch(error => {
78
+ console.error(`Error creating output directory: ${error.message}`);
79
+ process.exit(1);
80
+ });
81
+ }
82
+
83
+ // Build the module
84
+ const { cfg, path, uuid } = await buildModule(undefined, opts.entryPoint, opts.embed);
85
+
86
+ // Files to include in controller image for WASM support
87
+ const { includedFiles } = cfg.pepr;
88
+
89
+ let image: string = "";
90
+
91
+ // Build Kubernetes manifests with custom image
92
+ if (opts.customImage) {
93
+ if (opts.registry) {
94
+ console.error(`Custom Image and registry cannot be used together.`);
95
+ process.exit(1);
96
+ }
97
+ image = opts.customImage;
98
+ }
99
+
100
+ // Check if there is a custom timeout defined
101
+ if (opts.timeout !== undefined) {
102
+ cfg.pepr.webhookTimeout = opts.timeout;
103
+ }
104
+
105
+ if (opts.registryInfo !== undefined) {
106
+ console.info(`Including ${includedFiles.length} files in controller image.`);
107
+
108
+ // for journey test to make sure the image is built
109
+ image = `${opts.registryInfo}/custom-pepr-controller:${cfg.pepr.peprVersion}`;
110
+
111
+ // only actually build/push if there are files to include
112
+ if (includedFiles.length > 0) {
113
+ await createDockerfile(cfg.pepr.peprVersion, cfg.description, includedFiles);
114
+ execSync(`docker build --tag ${image} -f Dockerfile.controller .`, { stdio: "inherit" });
115
+ execSync(`docker push ${image}`, { stdio: "inherit" });
116
+ }
117
+ }
118
+
119
+ // If building without embedding, exit after building
120
+ if (!opts.embed) {
121
+ console.info(`✅ Module built successfully at ${path}`);
122
+ return;
123
+ }
124
+
125
+ // set the image version if provided
126
+ if (opts.version) {
127
+ cfg.pepr.peprVersion = opts.version;
128
+ }
129
+
130
+ // Generate a secret for the module
131
+ const assets = new Assets(
132
+ {
133
+ ...cfg.pepr,
134
+ appVersion: cfg.version,
135
+ description: cfg.description,
136
+ },
137
+ path,
138
+ );
139
+
140
+ // If registry is set to Iron Bank, use Iron Bank image
141
+ if (opts?.registry == "Iron Bank") {
142
+ console.warn(
143
+ `\n\tThis command assumes the latest release. Pepr's Iron Bank image release cycle is dictated by renovate and is typically released a few days after the GitHub release.\n\tAs an alternative you may consider custom --custom-image to target a specific image and version.`,
144
+ );
145
+ image = `registry1.dso.mil/ironbank/opensource/defenseunicorns/pepr/controller:v${cfg.pepr.peprVersion}`;
146
+ }
147
+
148
+ // if image is a custom image, use that instead of the default
149
+ if (image !== "") {
150
+ assets.image = image;
151
+ }
152
+
153
+ // Ensure imagePullSecret is valid
154
+ if (opts.withPullSecret) {
155
+ if (sanitizeResourceName(opts.withPullSecret) !== opts.withPullSecret) {
156
+ // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
157
+ console.error(
158
+ "Invalid imagePullSecret. Please provide a valid name as defined in RFC 1123.",
159
+ );
160
+ process.exit(1);
161
+ }
162
+ }
163
+
164
+ const yamlFile = `pepr-module-${uuid}.yaml`;
165
+ const chartPath = `${uuid}-chart`;
166
+ const yamlPath = resolve(outputDir, yamlFile);
167
+ const yaml = await assets.allYaml(opts.rbacMode, opts.withPullSecret);
168
+
169
+ try {
170
+ // wait for capabilities to be loaded and test names
171
+ validateCapabilityNames(assets.capabilities);
172
+ } catch (e) {
173
+ console.error(`Error loading capability:`, e);
174
+ process.exit(1);
175
+ }
176
+
177
+ const zarfPath = resolve(outputDir, "zarf.yaml");
178
+
179
+ let zarf = "";
180
+ if (opts.zarf === "chart") {
181
+ zarf = assets.zarfYamlChart(chartPath);
182
+ } else {
183
+ zarf = assets.zarfYaml(yamlFile);
184
+ }
185
+ await fs.writeFile(yamlPath, yaml);
186
+ await fs.writeFile(zarfPath, zarf);
187
+
188
+ await assets.generateHelmChart(outputDir);
189
+
190
+ console.info(`✅ K8s resource for the module saved to ${yamlPath}`);
191
+ });
192
+ }
193
+
194
+ // Create a list of external libraries to exclude from the bundle, these are already stored in the container
195
+ const externalLibs = Object.keys(dependencies);
196
+
197
+ // Add the pepr library to the list of external libraries
198
+ externalLibs.push("pepr");
199
+
200
+ // Add the kubernetes client to the list of external libraries as it is pulled in by kubernetes-fluent-client
201
+ externalLibs.push("@kubernetes/client-node");
202
+
203
+ export async function loadModule(entryPoint = peprTS) {
204
+ // Resolve path to the module / files
205
+ const entryPointPath = resolve(".", entryPoint);
206
+ const modulePath = dirname(entryPointPath);
207
+ const cfgPath = resolve(modulePath, "package.json");
208
+
209
+ // Ensure the module's package.json and entrypoint files exist
210
+ try {
211
+ await fs.access(cfgPath);
212
+ await fs.access(entryPointPath);
213
+ } catch (e) {
214
+ console.error(
215
+ `Could not find ${cfgPath} or ${entryPointPath} in the current directory. Please run this command from the root of your module's directory.`,
216
+ );
217
+ process.exit(1);
218
+ }
219
+
220
+ // Read the module's UUID from the package.json file
221
+ const moduleText = await fs.readFile(cfgPath, { encoding: "utf-8" });
222
+ const cfg = JSON.parse(moduleText);
223
+ const { uuid } = cfg.pepr;
224
+ const name = `pepr-${uuid}.js`;
225
+
226
+ // Set the Pepr version from the current running version
227
+ cfg.pepr.peprVersion = version;
228
+
229
+ // Exit if the module's UUID could not be found
230
+ if (!uuid) {
231
+ throw new Error("Could not load the uuid in package.json");
232
+ }
233
+
234
+ return {
235
+ cfg,
236
+ entryPointPath,
237
+ modulePath,
238
+ name,
239
+ path: resolve(outputDir, name),
240
+ uuid,
241
+ };
242
+ }
243
+
244
+ export async function buildModule(reloader?: Reloader, entryPoint = peprTS, embed = true) {
245
+ try {
246
+ const { cfg, modulePath, path, uuid } = await loadModule(entryPoint);
247
+
248
+ const validFormat = await peprFormat(true);
249
+
250
+ if (!validFormat) {
251
+ console.log(
252
+ "\x1b[33m%s\x1b[0m",
253
+ "Formatting errors were found. The build will continue, but you may want to run `npx pepr format` to address any issues.",
254
+ );
255
+ }
256
+
257
+ // Resolve node_modules folder (in support of npm workspaces!)
258
+ const npmRoot = execFileSync("npm", ["root"]).toString().trim();
259
+
260
+ // Run `tsc` to validate the module's types & output sourcemaps
261
+ const args = ["--project", `${modulePath}/tsconfig.json`, "--outdir", outputDir];
262
+ execFileSync(`${npmRoot}/.bin/tsc`, args);
263
+
264
+ // Common build options for all builds
265
+ const ctxCfg: BuildOptions = {
266
+ bundle: true,
267
+ entryPoints: [entryPoint],
268
+ external: externalLibs,
269
+ format: "cjs",
270
+ keepNames: true,
271
+ legalComments: "external",
272
+ metafile: true,
273
+ minify: true,
274
+ outfile: path,
275
+ plugins: [
276
+ {
277
+ name: "reload-server",
278
+ setup(build) {
279
+ build.onEnd(async r => {
280
+ // Print the build size analysis
281
+ if (r?.metafile) {
282
+ console.log(await analyzeMetafile(r.metafile));
283
+ }
284
+
285
+ // If we're in dev mode, call the reloader function
286
+ if (reloader) {
287
+ await reloader(r);
288
+ }
289
+ });
290
+ },
291
+ },
292
+ ],
293
+ platform: "node",
294
+ sourcemap: true,
295
+ treeShaking: true,
296
+ };
297
+
298
+ if (reloader) {
299
+ // Only minify the code if we're not in dev mode
300
+ ctxCfg.minify = false;
301
+ }
302
+
303
+ // If not embedding (i.e. making a library module to be distro'd via NPM)
304
+ if (!embed) {
305
+ // Don't minify
306
+ ctxCfg.minify = false;
307
+
308
+ // Preserve the original file name
309
+ ctxCfg.outfile = resolve(outputDir, basename(entryPoint, extname(entryPoint))) + ".js";
310
+
311
+ // Don't bundle
312
+ ctxCfg.packages = "external";
313
+
314
+ // Don't tree shake
315
+ ctxCfg.treeShaking = false;
316
+ }
317
+
318
+ const ctx = await context(ctxCfg);
319
+
320
+ // If the reloader function is defined, watch the module for changes
321
+ if (reloader) {
322
+ await ctx.watch();
323
+ } else {
324
+ // Otherwise, just build the module once
325
+ await ctx.rebuild();
326
+ await ctx.dispose();
327
+ }
328
+
329
+ return { ctx, path, cfg, uuid };
330
+ } catch (e) {
331
+ console.error(`Error building module:`, e);
332
+
333
+ if (e.stdout) {
334
+ const out = e.stdout.toString() as string;
335
+ const err = e.stderr.toString();
336
+
337
+ console.log(out);
338
+ console.error(err);
339
+
340
+ // Check for version conflicts
341
+ if (out.includes("Types have separate declarations of a private property '_name'.")) {
342
+ // Try to find the conflicting package
343
+ const pgkErrMatch = /error TS2322: .*? 'import\("\/.*?\/node_modules\/(.*?)\/node_modules/g;
344
+ out.matchAll(pgkErrMatch);
345
+
346
+ // Look for package conflict errors
347
+ const conflicts = [...out.matchAll(pgkErrMatch)];
348
+
349
+ // If the regex didn't match, leave a generic error
350
+ if (conflicts.length < 1) {
351
+ console.warn(
352
+ `\n\tOne or more imported Pepr Capabilities seem to be using an incompatible version of Pepr.\n\tTry updating your Pepr Capabilities to their latest versions.`,
353
+ "Version Conflict",
354
+ );
355
+ }
356
+
357
+ // Otherwise, loop through each conflicting package and print an error
358
+ conflicts.forEach(match => {
359
+ console.warn(
360
+ `\n\tPackage '${match[1]}' seems to be incompatible with your current version of Pepr.\n\tTry updating to the latest version.`,
361
+ "Version Conflict",
362
+ );
363
+ });
364
+ }
365
+ }
366
+
367
+ // On any other error, exit with a non-zero exit code
368
+ process.exit(1);
369
+ }
370
+ }
@@ -0,0 +1,105 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import prompt from "prompts";
5
+
6
+ import { Assets } from "../lib/assets";
7
+ import { buildModule } from "./build";
8
+ import { RootCmd } from "./root";
9
+ import { validateCapabilityNames, namespaceDeploymentsReady } from "../lib/helpers";
10
+ import { ImagePullSecret } from "../lib/types";
11
+ import { sanitizeName } from "./init/utils";
12
+ import { deployImagePullSecret } from "../lib/assets/deploy";
13
+
14
+ export default function (program: RootCmd) {
15
+ program
16
+ .command("deploy")
17
+ .description("Deploy a Pepr Module")
18
+ .option("-i, --image [image]", "Override the image tag")
19
+ .option("--confirm", "Skip confirmation prompt")
20
+ .option("--pullSecret <name>", "Deploy imagePullSecret for Controller private registry")
21
+ .option("--docker-server <server>", "Docker server address")
22
+ .option("--docker-username <username>", "Docker registry username")
23
+ .option("--docker-email <email>", "Email for Docker registry")
24
+ .option("--docker-password <password>", "Password for Docker registry")
25
+ .option("--force", "Force deploy the module, override manager field")
26
+ .action(async opts => {
27
+ let imagePullSecret: ImagePullSecret | undefined;
28
+
29
+ if (
30
+ opts.pullSecret &&
31
+ opts.pullSecret.length > 0 &&
32
+ (!opts.dockerServer || !opts.dockerUsername || !opts.dockerEmail || !opts.dockerPassword)
33
+ ) {
34
+ console.error(
35
+ "Error: Must provide docker server, username, email, and password when providing pull secret",
36
+ );
37
+ process.exit(1);
38
+ } else if (opts.pullSecret && opts.pullSecret !== sanitizeName(opts.pullSecret)) {
39
+ // https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#dns-subdomain-names
40
+ console.error(
41
+ "Invalid imagePullSecret name. Please provide a valid name as defined in RFC 1123.",
42
+ );
43
+ process.exit(1);
44
+ } else if (opts.pullSecret) {
45
+ imagePullSecret = {
46
+ auths: {
47
+ [opts.dockerServer]: {
48
+ username: opts.dockerUsername,
49
+ password: opts.dockerPassword,
50
+ email: opts.dockerEmail,
51
+ auth: Buffer.from(`${opts.dockerUsername}:${opts.dockerPassword}`).toString("base64"),
52
+ },
53
+ },
54
+ };
55
+
56
+ await deployImagePullSecret(imagePullSecret, opts.pullSecret);
57
+ return;
58
+ }
59
+
60
+ if (!opts.confirm) {
61
+ // Prompt the user to confirm
62
+ const confirm = await prompt({
63
+ type: "confirm",
64
+ name: "confirm",
65
+ message: "This will remove and redeploy the module. Continue?",
66
+ });
67
+
68
+ // Exit if the user doesn't confirm
69
+ if (!confirm.confirm) {
70
+ process.exit(0);
71
+ }
72
+ }
73
+
74
+ // Build the module
75
+ const { cfg, path } = await buildModule();
76
+
77
+ // Generate a secret for the module
78
+ const webhook = new Assets(
79
+ {
80
+ ...cfg.pepr,
81
+ description: cfg.description,
82
+ },
83
+ path,
84
+ );
85
+
86
+ if (opts.image) {
87
+ webhook.image = opts.image;
88
+ }
89
+
90
+ // Identify conf'd webhookTimeout to give to deploy call
91
+ const timeout = cfg.pepr.webhookTimeout ? cfg.pepr.webhookTimeout : 10;
92
+
93
+ try {
94
+ await webhook.deploy(opts.force, timeout);
95
+ // wait for capabilities to be loaded and test names
96
+ validateCapabilityNames(webhook.capabilities);
97
+ // Wait for the pepr-system resources to be fully up
98
+ await namespaceDeploymentsReady();
99
+ console.info(`✅ Module deployed successfully`);
100
+ } catch (e) {
101
+ console.error(`Error deploying module:`, e);
102
+ process.exit(1);
103
+ }
104
+ });
105
+ }
package/src/cli/dev.ts ADDED
@@ -0,0 +1,118 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { ChildProcess, fork } from "child_process";
5
+ import { promises as fs } from "fs";
6
+ import prompt from "prompts";
7
+ import { validateCapabilityNames } from "../lib/helpers";
8
+ import { Assets } from "../lib/assets";
9
+ import { buildModule, loadModule } from "./build";
10
+ import { RootCmd } from "./root";
11
+ import { K8s, kind } from "kubernetes-fluent-client";
12
+ import { PeprStore } from "../lib/k8s";
13
+ export default function (program: RootCmd) {
14
+ program
15
+ .command("dev")
16
+ .description("Setup a local webhook development environment")
17
+ .option("-h, --host [host]", "Host to listen on", "host.k3d.internal")
18
+ .option("--confirm", "Skip confirmation prompt")
19
+ .action(async opts => {
20
+ // Prompt the user to confirm if they didn't pass the --confirm flag
21
+ if (!opts.confirm) {
22
+ const confirm = await prompt({
23
+ type: "confirm",
24
+ name: "confirm",
25
+ message: "This will remove and redeploy the module. Continue?",
26
+ });
27
+
28
+ // Exit if the user doesn't confirm
29
+ if (!confirm.confirm) {
30
+ process.exit(0);
31
+ }
32
+ }
33
+
34
+ // Build the module
35
+ const { cfg, path } = await loadModule();
36
+
37
+ // Generate a secret for the module
38
+ const webhook = new Assets(
39
+ {
40
+ ...cfg.pepr,
41
+ description: cfg.description,
42
+ },
43
+ path,
44
+ opts.host,
45
+ );
46
+
47
+ // Write the TLS cert and key to disk
48
+ await fs.writeFile("insecure-tls.crt", webhook.tls.pem.crt);
49
+ await fs.writeFile("insecure-tls.key", webhook.tls.pem.key);
50
+
51
+ try {
52
+ let program: ChildProcess;
53
+ const name = `pepr-${cfg.pepr.uuid}`;
54
+ const scheduleStore = `pepr-${cfg.pepr.uuid}-schedule`;
55
+ const store = `pepr-${cfg.pepr.uuid}-store`;
56
+
57
+ // Run the processed javascript file
58
+ const runFork = async () => {
59
+ console.info(`Running module ${path}`);
60
+
61
+ // Deploy the webhook with a 30 second timeout for debugging, don't force
62
+ await webhook.deploy(false, 30);
63
+
64
+ try {
65
+ // wait for capabilities to be loaded and test names
66
+ validateCapabilityNames(webhook.capabilities);
67
+ } catch (e) {
68
+ console.error(`Error validating capability names:`, e);
69
+ process.exit(1);
70
+ }
71
+
72
+ program = fork(path, {
73
+ env: {
74
+ ...process.env,
75
+ LOG_LEVEL: "debug",
76
+ PEPR_MODE: "dev",
77
+ PEPR_API_TOKEN: webhook.apiToken,
78
+ PEPR_PRETTY_LOGS: "true",
79
+ SSL_KEY_PATH: "insecure-tls.key",
80
+ SSL_CERT_PATH: "insecure-tls.crt",
81
+ },
82
+ stdio: "inherit",
83
+ });
84
+
85
+ program.on("close", async () => {
86
+ await Promise.all([
87
+ K8s(kind.MutatingWebhookConfiguration).Delete(name),
88
+ K8s(kind.ValidatingWebhookConfiguration).Delete(name),
89
+ K8s(PeprStore).InNamespace("pepr-system").Delete(scheduleStore),
90
+ K8s(PeprStore).InNamespace("pepr-system").Delete(store),
91
+ ]);
92
+ });
93
+
94
+ // listen for CTRL+C and remove webhooks
95
+ process.on("SIGINT", () => {
96
+ console.debug(`Received SIGINT, removing webhooks`);
97
+ });
98
+ };
99
+
100
+ await buildModule(async r => {
101
+ if (r.errors.length > 0) {
102
+ console.error(`Error compiling module: ${r.errors}`);
103
+ return;
104
+ }
105
+
106
+ if (program) {
107
+ program.once("exit", runFork);
108
+ program.kill("SIGKILL");
109
+ } else {
110
+ await runFork();
111
+ }
112
+ });
113
+ } catch (e) {
114
+ console.error(`Error deploying module:`, e);
115
+ process.exit(1);
116
+ }
117
+ });
118
+ }
@@ -0,0 +1,83 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { ESLint } from "eslint";
5
+ import { promises as fs } from "fs";
6
+ import { format, resolveConfig } from "prettier";
7
+
8
+ import { RootCmd } from "./root";
9
+
10
+ export default function (program: RootCmd) {
11
+ program
12
+ .command("format")
13
+ .description("Lint and format this Pepr module")
14
+ .option("-v, --validate-only", "Do not modify files, only validate formatting")
15
+ .action(async opts => {
16
+ const success = await peprFormat(opts.validateOnly);
17
+
18
+ if (success) {
19
+ console.info("✅ Module formatted");
20
+ } else {
21
+ process.exit(1);
22
+ }
23
+ });
24
+ }
25
+
26
+ /**
27
+ * Perform linting and formatting on the module
28
+ * @param validateOnly
29
+ * @returns success
30
+ */
31
+ export async function peprFormat(validateOnly: boolean) {
32
+ {
33
+ try {
34
+ const eslint = new ESLint();
35
+ const results = await eslint.lintFiles(["./**/*.ts"]);
36
+
37
+ // Track if any files failed
38
+ let hasFailure = false;
39
+
40
+ results.forEach(async result => {
41
+ const errorCount = result.fatalErrorCount + result.errorCount;
42
+ if (errorCount > 0) {
43
+ hasFailure = true;
44
+ }
45
+ });
46
+
47
+ const formatter = await eslint.loadFormatter("stylish");
48
+ const resultText = await formatter.format(results, {} as ESLint.LintResultData);
49
+
50
+ if (resultText) {
51
+ console.log(resultText);
52
+ }
53
+
54
+ // Write the fixes if not in validate-only mode
55
+ if (!validateOnly) {
56
+ await ESLint.outputFixes(results);
57
+ }
58
+
59
+ // Format with Prettier
60
+ for (const { filePath } of results) {
61
+ const content = await fs.readFile(filePath, "utf8");
62
+ const cfg = await resolveConfig(filePath);
63
+ const formatted = await format(content, { filepath: filePath, ...cfg });
64
+
65
+ // If in validate-only mode, check if the file is formatted correctly
66
+ if (validateOnly) {
67
+ if (formatted !== content) {
68
+ hasFailure = true;
69
+ console.error(`File ${filePath} is not formatted correctly`);
70
+ }
71
+ } else {
72
+ // Otherwise, write the formatted file
73
+ await fs.writeFile(filePath, formatted);
74
+ }
75
+ }
76
+
77
+ return !hasFailure;
78
+ } catch (e) {
79
+ console.error(`Error formatting module:`, e);
80
+ return false;
81
+ }
82
+ }
83
+ }