pepr 0.33.0 → 0.34.1
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/README.md +2 -1
- package/dist/cli/banner.d.ts +2 -0
- package/dist/cli/banner.d.ts.map +1 -0
- package/dist/cli/build.d.ts +19 -0
- package/dist/cli/build.d.ts.map +1 -0
- package/dist/cli/deploy.d.ts +3 -0
- package/dist/cli/deploy.d.ts.map +1 -0
- package/dist/cli/dev.d.ts +3 -0
- package/dist/cli/dev.d.ts.map +1 -0
- package/dist/cli/format.d.ts +9 -0
- package/dist/cli/format.d.ts.map +1 -0
- package/dist/cli/init/index.d.ts +3 -0
- package/dist/cli/init/index.d.ts.map +1 -0
- package/dist/cli/init/templates.d.ts +196 -0
- package/dist/cli/init/templates.d.ts.map +1 -0
- package/dist/cli/init/utils.d.ts +21 -0
- package/dist/cli/init/utils.d.ts.map +1 -0
- package/dist/cli/init/utils.test.d.ts +2 -0
- package/dist/cli/init/utils.test.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.d.ts +8 -0
- package/dist/cli/init/walkthrough.d.ts.map +1 -0
- package/dist/cli/init/walkthrough.test.d.ts +2 -0
- package/dist/cli/init/walkthrough.test.d.ts.map +1 -0
- package/dist/cli/kfc.d.ts +3 -0
- package/dist/cli/kfc.d.ts.map +1 -0
- package/dist/cli/monitor.d.ts +3 -0
- package/dist/cli/monitor.d.ts.map +1 -0
- package/dist/cli/root.d.ts +5 -0
- package/dist/cli/root.d.ts.map +1 -0
- package/dist/cli/update.d.ts +3 -0
- package/dist/cli/update.d.ts.map +1 -0
- package/dist/cli/uuid.d.ts +3 -0
- package/dist/cli/uuid.d.ts.map +1 -0
- package/dist/cli.js +69 -39
- package/dist/controller.js +1 -2
- package/dist/fixtures/loader.d.ts +5 -0
- package/dist/fixtures/loader.d.ts.map +1 -0
- package/dist/lib/assets/helm.d.ts +1 -0
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/helm.test.d.ts +2 -0
- package/dist/lib/assets/helm.test.d.ts.map +1 -0
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +3 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/pods.test.d.ts +2 -0
- package/dist/lib/assets/pods.test.d.ts.map +1 -0
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/errors.test.d.ts +2 -0
- package/dist/lib/errors.test.d.ts.map +1 -0
- package/dist/lib/filter.test.d.ts +2 -0
- package/dist/lib/filter.test.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +0 -5
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/helpers.test.d.ts +2 -0
- package/dist/lib/helpers.test.d.ts.map +1 -0
- package/dist/lib/included-files.test.d.ts +2 -0
- package/dist/lib/included-files.test.d.ts.map +1 -0
- package/dist/lib/logger.test.d.ts +2 -0
- package/dist/lib/logger.test.d.ts.map +1 -0
- package/dist/lib/metrics.d.ts +18 -0
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/metrics.test.d.ts +2 -0
- package/dist/lib/metrics.test.d.ts.map +1 -0
- package/dist/lib/module.test.d.ts +2 -0
- package/dist/lib/module.test.d.ts.map +1 -0
- package/dist/lib/mutate-request.test.d.ts +2 -0
- package/dist/lib/mutate-request.test.d.ts.map +1 -0
- package/dist/lib/queue.test.d.ts +2 -0
- package/dist/lib/queue.test.d.ts.map +1 -0
- package/dist/lib/schedule.test.d.ts +15 -0
- package/dist/lib/schedule.test.d.ts.map +1 -0
- package/dist/lib/storage.d.ts +1 -0
- package/dist/lib/storage.d.ts.map +1 -1
- package/dist/lib/storage.test.d.ts +2 -0
- package/dist/lib/storage.test.d.ts.map +1 -0
- package/dist/lib/tls.test.d.ts +2 -0
- package/dist/lib/tls.test.d.ts.map +1 -0
- package/dist/lib/utils.test.d.ts +2 -0
- package/dist/lib/utils.test.d.ts.map +1 -0
- package/dist/lib/validate-request.test.d.ts +2 -0
- package/dist/lib/validate-request.test.d.ts.map +1 -0
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib/watch-processor.test.d.ts +2 -0
- package/dist/lib/watch-processor.test.d.ts.map +1 -0
- package/dist/lib.js +86 -24
- package/dist/lib.js.map +3 -3
- package/dist/sdk/sdk.test.d.ts +2 -0
- package/dist/sdk/sdk.test.d.ts.map +1 -0
- package/package.json +22 -16
- package/src/cli/banner.ts +63 -0
- package/src/cli/build.ts +370 -0
- package/src/cli/deploy.ts +105 -0
- package/src/cli/dev.ts +118 -0
- package/src/cli/format.ts +83 -0
- package/src/cli/init/index.ts +99 -0
- package/src/cli/init/templates.ts +124 -0
- package/src/cli/init/utils.test.ts +28 -0
- package/src/cli/init/utils.ts +55 -0
- package/src/cli/init/walkthrough.test.ts +21 -0
- package/src/cli/init/walkthrough.ts +96 -0
- package/src/cli/kfc.ts +45 -0
- package/src/cli/monitor.ts +101 -0
- package/src/cli/root.ts +12 -0
- package/src/cli/update.ts +95 -0
- package/src/cli/uuid.ts +44 -0
- package/src/fixtures/data/create-pod.json +271 -0
- package/src/fixtures/data/delete-pod.json +271 -0
- package/src/fixtures/loader.ts +18 -0
- package/src/lib/.prettierrc +14 -0
- package/src/lib/assets/helm.test.ts +64 -0
- package/src/lib/assets/helm.ts +35 -0
- package/src/lib/assets/index.ts +5 -1
- package/src/lib/assets/pods.test.ts +553 -0
- package/src/lib/assets/pods.ts +14 -6
- package/src/lib/assets/yaml.ts +15 -15
- package/src/lib/controller/index.ts +2 -2
- package/src/lib/controller/store.ts +0 -2
- package/src/lib/errors.test.ts +85 -0
- package/src/lib/filter.test.ts +384 -0
- package/src/lib/helpers.test.ts +1192 -0
- package/src/lib/helpers.ts +0 -17
- package/src/lib/included-files.test.ts +22 -0
- package/src/lib/logger.test.ts +18 -0
- package/src/lib/metrics.test.ts +132 -0
- package/src/lib/metrics.ts +68 -6
- package/src/lib/module.test.ts +126 -0
- package/src/lib/mutate-request.test.ts +188 -0
- package/src/lib/queue.test.ts +58 -0
- package/src/lib/schedule.test.ts +217 -0
- package/src/lib/storage.test.ts +216 -0
- package/src/lib/storage.ts +12 -4
- package/src/lib/tls.test.ts +18 -0
- package/src/lib/utils.test.ts +69 -0
- package/src/lib/validate-request.test.ts +124 -0
- package/src/lib/watch-processor.test.ts +322 -0
- package/src/lib/watch-processor.ts +20 -4
- package/src/sdk/sdk.test.ts +243 -0
- package/src/templates/.eslintrc.json +6 -0
- package/src/templates/capabilities/hello-pepr.ts +26 -11
- package/.prettierignore +0 -1
- package/CODE_OF_CONDUCT.md +0 -133
- package/SECURITY.md +0 -18
- package/SUPPORT.md +0 -16
- package/codecov.yaml +0 -19
- package/commitlint.config.js +0 -1
package/src/cli/build.ts
ADDED
|
@@ -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
|
+
}
|