pepr 0.45.1 → 0.46.0-nightly.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/package.json CHANGED
@@ -15,7 +15,7 @@
15
15
  "!src/**/*.test.ts",
16
16
  "!dist/**/*.test.d.ts*"
17
17
  ],
18
- "version": "0.45.1",
18
+ "version": "0.46.0-nightly.1",
19
19
  "main": "dist/lib.js",
20
20
  "types": "dist/lib.d.ts",
21
21
  "scripts": {
@@ -24,6 +24,7 @@
24
24
  "prebuild": "rm -fr dist/* && npm run gen-data-json",
25
25
  "build": "tsc && node build.mjs && npm pack",
26
26
  "build:image": "npm run build && docker buildx build --output type=docker --tag pepr:dev .",
27
+ "build:image:unicorn": "npm run build && docker buildx build -f Dockerfile.unicorn --output type=docker --tag pepr:dev .",
27
28
  "set:version": "node scripts/set-version.js",
28
29
  "test": "npm run test:unit && npm run test:journey && npm run test:journey-wasm",
29
30
  "test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage --testPathIgnorePatterns='cosign.e2e.test.ts'",
@@ -31,9 +32,12 @@
31
32
  "test:integration:prep": "./integration/prep.sh",
32
33
  "test:integration:run": "jest --maxWorkers=4 integration",
33
34
  "test:journey": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run",
35
+ "test:journey:unicorn": "npm run test:journey:k3d && npm run build && npm run test:journey:image:unicorn && npm run test:journey:run",
34
36
  "test:journey-wasm": "npm run test:journey:k3d && npm run build && npm run test:journey:image && npm run test:journey:run-wasm",
37
+ "test:journey-wasm:unicorn": "npm run test:journey:k3d && npm run build && npm run test:journey:image:unicorn && npm run test:journey:run-wasm",
35
38
  "test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system",
36
39
  "test:journey:image": "docker buildx build --output type=docker --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev",
40
+ "test:journey:image:unicorn": "docker buildx build -f Dockerfile.unicorn --output type=docker --tag pepr:dev . && k3d image import pepr:dev -c pepr-dev",
37
41
  "test:journey:run": "jest --detectOpenHandles journey/entrypoint.test.ts && npm run test:journey:upgrade",
38
42
  "test:journey:run-wasm": "jest --detectOpenHandles journey/entrypoint-wasm.test.ts",
39
43
  "test:journey:upgrade": "npm run test:journey:k3d && npm run test:journey:image && jest --detectOpenHandles journey/pepr-upgrade.test.ts",
@@ -76,7 +80,8 @@
76
80
  "undici": "^7.0.1"
77
81
  },
78
82
  "overrides": {
79
- "glob": "^9.0.0"
83
+ "glob": "^9.0.0",
84
+ "jsonpath-plus": "^10.3.0"
80
85
  },
81
86
  "peerDependencies": {
82
87
  "@types/prompts": "2.4.9",
@@ -91,4 +96,4 @@
91
96
  "typescript": "5.7.3",
92
97
  "uuid": "11.0.5"
93
98
  }
94
- }
99
+ }
@@ -94,7 +94,7 @@ export async function handleCustomOutputDir(outputDir: string): Promise<string>
94
94
  * Check if the image is from Iron Bank and return the correct image
95
95
  * @param registry The registry of the image
96
96
  * @param image The image to check
97
- * @param peprVersion The version of the PEPR controller
97
+ * @param peprVersion The version of the Pepr controller
98
98
  * @returns The image string
99
99
  * @example
100
100
  */
package/src/cli/build.ts CHANGED
@@ -77,13 +77,13 @@ export default function (program: RootCmd): void {
77
77
  new Option(
78
78
  "-i, --custom-image <custom-image>",
79
79
  "Specify a custom image (including version) for Admission and Watch Deployments. Example: 'docker.io/username/custom-pepr-controller:v1.0.0'",
80
- ).conflicts(["version", "registryInfo", "registry"]),
80
+ ).conflicts(["registryInfo", "registry"]),
81
81
  )
82
82
  .addOption(
83
83
  new Option(
84
84
  "-r, --registry-info [<registry>/<username>]",
85
85
  "Provide the image registry and username for building and pushing a custom WASM container. Requires authentication. Builds and pushes 'registry/username/custom-pepr-controller:<current-version>'.",
86
- ).conflicts(["customImage", "version", "registry"]),
86
+ ).conflicts(["customImage", "registry"]),
87
87
  )
88
88
 
89
89
  .option("-o, --output-dir <output directory>", "Define where to place build output")
@@ -92,12 +92,6 @@ export default function (program: RootCmd): void {
92
92
  "How long the API server should wait for a webhook to respond before treating the call as a failure",
93
93
  parseTimeout,
94
94
  )
95
- .addOption(
96
- new Option(
97
- "-v, --version <version>",
98
- "DEPRECATED: The version of the Pepr image to use in the deployment manifests. Example: '0.27.3'.",
99
- ).conflicts(["customImage", "registryInfo"]),
100
- )
101
95
  .option(
102
96
  "--withPullSecret <imagePullSecret>",
103
97
  "Image Pull Secret: Use image pull secret for controller Deployment.",
@@ -164,8 +158,6 @@ export default function (program: RootCmd): void {
164
158
  console.info(`✅ Module built successfully at ${path}`);
165
159
  return;
166
160
  }
167
- // set the image version if provided -- DEPRECATED
168
- if (opts.version) cfg.pepr.peprVersion = opts.version;
169
161
 
170
162
  // Generate a secret for the module
171
163
  const assets = new Assets(
package/src/cli/dev.ts CHANGED
@@ -77,7 +77,7 @@ export default function (program: RootCmd): void {
77
77
  ...process.env,
78
78
  LOG_LEVEL: "debug",
79
79
  PEPR_MODE: "dev",
80
- PEPR_API_TOKEN: webhook.apiToken,
80
+ PEPR_API_PATH: webhook.apiPath,
81
81
  PEPR_PRETTY_LOGS: "true",
82
82
  SSL_KEY_PATH: "insecure-tls.key",
83
83
  SSL_CERT_PATH: "insecure-tls.crt",
@@ -24,13 +24,13 @@ import { loadCapabilities } from "./loader";
24
24
  import { namespaceComplianceValidator, dedent } from "../helpers";
25
25
  import { promises as fs } from "fs";
26
26
  import { storeRole, storeRoleBinding, clusterRoleBinding, serviceAccount } from "./rbac";
27
- import { watcherService, service, tlsSecret, apiTokenSecret } from "./networking";
27
+ import { watcherService, service, tlsSecret, apiPathSecret } from "./networking";
28
28
  import { WebhookType } from "../enums";
29
29
 
30
30
  export class Assets {
31
31
  readonly name: string;
32
32
  readonly tls: TLSOut;
33
- readonly apiToken: string;
33
+ readonly apiPath: string;
34
34
  readonly config: ModuleConfig;
35
35
  readonly path: string;
36
36
  readonly alwaysIgnore!: WebhookIgnore;
@@ -53,8 +53,8 @@ export class Assets {
53
53
  // Generate the ephemeral tls things
54
54
  this.tls = genTLS(host || `${this.name}.pepr-system.svc`);
55
55
 
56
- // Generate the api token for the controller / webhook
57
- this.apiToken = crypto.randomBytes(32).toString("hex");
56
+ // Generate the api path for the controller / webhook
57
+ this.apiPath = crypto.randomBytes(32).toString("hex");
58
58
  }
59
59
 
60
60
  async deploy(
@@ -149,7 +149,7 @@ export class Assets {
149
149
  [helm.files.watcherServiceYaml, (): string => toYaml(watcherService(this.name))],
150
150
  [helm.files.admissionServiceYaml, (): string => toYaml(service(this.name))],
151
151
  [helm.files.tlsSecretYaml, (): string => toYaml(tlsSecret(this.name, this.tls))],
152
- [helm.files.apiTokenSecretYaml, (): string => toYaml(apiTokenSecret(this.name, this.apiToken))],
152
+ [helm.files.apiPathSecretYaml, (): string => toYaml(apiPathSecret(this.name, this.apiPath))],
153
153
  [helm.files.storeRoleYaml, (): string => toYaml(storeRole(this.name))],
154
154
  [helm.files.storeRoleBindingYaml, (): string => toYaml(storeRoleBinding(this.name))],
155
155
  [helm.files.clusterRoleYaml, (): string => dedent(clusterRoleTemplate())],
@@ -164,7 +164,7 @@ export class Assets {
164
164
  name: this.name,
165
165
  image: this.image,
166
166
  config: this.config,
167
- apiToken: this.apiToken,
167
+ apiPath: this.apiPath,
168
168
  capabilities: this.capabilities,
169
169
  };
170
170
  await overridesFile(overrideData, helm.files.valuesYaml, this.imagePullSecrets);
@@ -8,7 +8,7 @@ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
8
8
 
9
9
  import { Assets } from "./assets";
10
10
  import Log from "../telemetry/logger";
11
- import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
11
+ import { apiPathSecret, service, tlsSecret, watcherService } from "./networking";
12
12
  import { getDeployment, getModuleSecret, getNamespace, getWatcher } from "./pods";
13
13
  import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
14
14
  import { peprStoreCRD } from "./store";
@@ -136,9 +136,9 @@ async function setupController(assets: Assets, code: Buffer, hash: string, force
136
136
  const tls = tlsSecret(name, assets.tls);
137
137
  await K8s(kind.Secret).Apply(tls, { force });
138
138
 
139
- Log.info("Applying API token secret");
140
- const apiToken = apiTokenSecret(name, assets.apiToken);
141
- await K8s(kind.Secret).Apply(apiToken, { force });
139
+ Log.info("Applying API path secret");
140
+ const apiPath = apiPathSecret(name, assets.apiPath);
141
+ await K8s(kind.Secret).Apply(apiPath, { force });
142
142
 
143
143
  Log.info("Applying deployment");
144
144
  const dep = getDeployment(assets, hash, assets.buildTimestamp);
@@ -25,7 +25,7 @@ export async function destroyModule(name: string): Promise<void> {
25
25
  K8s(kind.Secret, { namespace }).Delete(`${name}-module`),
26
26
  K8s(kind.Service, { namespace }).Delete(name),
27
27
  K8s(kind.Secret, { namespace }).Delete(`${name}-tls`),
28
- K8s(kind.Secret, { namespace }).Delete(`${name}-api-token`),
28
+ K8s(kind.Secret, { namespace }).Delete(`${name}-api-path`),
29
29
  K8s(kind.Deployment, { namespace }).Delete(name),
30
30
  K8s(kind.Deployment, { namespace }).Delete(`${name}-watcher`),
31
31
  K8s(kind.Service, { namespace }).Delete(`${name}-watcher`),
@@ -221,8 +221,8 @@ export function admissionDeployTemplate(buildTimestamp: string): string {
221
221
  - name: tls-certs
222
222
  mountPath: /etc/certs
223
223
  readOnly: true
224
- - name: api-token
225
- mountPath: /app/api-token
224
+ - name: api-path
225
+ mountPath: /app/api-path
226
226
  readOnly: true
227
227
  - name: module
228
228
  mountPath: /app/load
@@ -234,9 +234,9 @@ export function admissionDeployTemplate(buildTimestamp: string): string {
234
234
  - name: tls-certs
235
235
  secret:
236
236
  secretName: {{ .Values.uuid }}-tls
237
- - name: api-token
237
+ - name: api-path
238
238
  secret:
239
- secretName: {{ .Values.uuid }}-api-token
239
+ secretName: {{ .Values.uuid }}-api-path
240
240
  - name: module
241
241
  secret:
242
242
  secretName: {{ .Values.uuid }}-module
@@ -80,7 +80,7 @@ export function helmLayout(basePath: string, unique: string): Record<string, Rec
80
80
  watcherDeploymentYaml: `${helm.dirs.tmpls}/watcher-deployment.yaml`,
81
81
  watcherServiceMonitorYaml: `${helm.dirs.tmpls}/watcher-service-monitor.yaml`,
82
82
  tlsSecretYaml: `${helm.dirs.tmpls}/tls-secret.yaml`,
83
- apiTokenSecretYaml: `${helm.dirs.tmpls}/api-token-secret.yaml`,
83
+ apiPathSecretYaml: `${helm.dirs.tmpls}/api-path-secret.yaml`,
84
84
  moduleSecretYaml: `${helm.dirs.tmpls}/module-secret.yaml`,
85
85
  storeRoleYaml: `${helm.dirs.tmpls}/store-role.yaml`,
86
86
  storeRoleBindingYaml: `${helm.dirs.tmpls}/store-role-binding.yaml`,
@@ -5,17 +5,17 @@ import { kind } from "kubernetes-fluent-client";
5
5
 
6
6
  import { TLSOut } from "../tls";
7
7
 
8
- export function apiTokenSecret(name: string, apiToken: string): kind.Secret {
8
+ export function apiPathSecret(name: string, apiPath: string): kind.Secret {
9
9
  return {
10
10
  apiVersion: "v1",
11
11
  kind: "Secret",
12
12
  metadata: {
13
- name: `${name}-api-token`,
13
+ name: `${name}-api-path`,
14
14
  namespace: "pepr-system",
15
15
  },
16
16
  type: "Opaque",
17
17
  data: {
18
- value: Buffer.from(apiToken).toString("base64"),
18
+ value: Buffer.from(apiPath).toString("base64"),
19
19
  },
20
20
  };
21
21
  }
@@ -297,8 +297,8 @@ export function getDeployment(
297
297
  readOnly: true,
298
298
  },
299
299
  {
300
- name: "api-token",
301
- mountPath: "/app/api-token",
300
+ name: "api-path",
301
+ mountPath: "/app/api-path",
302
302
  readOnly: true,
303
303
  },
304
304
  {
@@ -317,9 +317,9 @@ export function getDeployment(
317
317
  },
318
318
  },
319
319
  {
320
- name: "api-token",
320
+ name: "api-path",
321
321
  secret: {
322
- secretName: `${name}-api-token`,
322
+ secretName: `${name}-api-path`,
323
323
  },
324
324
  },
325
325
  {
@@ -75,7 +75,7 @@ export async function webhookConfigGenerator(
75
75
  ): Promise<kind.MutatingWebhookConfiguration | kind.ValidatingWebhookConfiguration | null> {
76
76
  const ignore: V1LabelSelectorRequirement[] = [];
77
77
 
78
- const { name, tls, config, apiToken, host } = assets;
78
+ const { name, tls, config, apiPath, host } = assets;
79
79
  const ignoreNS = concat(peprIgnoreNamespaces, resolveIgnoreNamespaces(config?.alwaysIgnore?.namespaces));
80
80
 
81
81
  // Add any namespaces to ignore
@@ -91,18 +91,18 @@ export async function webhookConfigGenerator(
91
91
  caBundle: tls.ca,
92
92
  };
93
93
 
94
- // The URL must include the API Token
95
- const apiPath = `/${mutateOrValidate}/${apiToken}`;
94
+ // The URL must include the API Path
95
+ const fullApiPath = `/${mutateOrValidate}/${apiPath}`;
96
96
 
97
97
  // If a host is specified, use that with a port of 3000
98
98
  if (host) {
99
- clientConfig.url = `https://${host}:3000${apiPath}`;
99
+ clientConfig.url = `https://${host}:3000${fullApiPath}`;
100
100
  } else {
101
101
  // Otherwise, use the service
102
102
  clientConfig.service = {
103
103
  name: name,
104
104
  namespace: "pepr-system",
105
- path: apiPath,
105
+ path: fullApiPath,
106
106
  };
107
107
  }
108
108
 
@@ -4,7 +4,7 @@
4
4
  import crypto from "crypto";
5
5
  import { Assets } from "../assets";
6
6
  import { WebhookType } from "../../enums";
7
- import { apiTokenSecret, service, tlsSecret, watcherService } from "../networking";
7
+ import { apiPathSecret, service, tlsSecret, watcherService } from "../networking";
8
8
  import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "../rbac";
9
9
  import { dumpYaml, V1Deployment } from "@kubernetes/client-node";
10
10
  import { getModuleSecret, getNamespace } from "../pods";
@@ -14,7 +14,7 @@ import { webhookConfigGenerator } from "../webhooks";
14
14
  type deployments = { default: V1Deployment; watch: V1Deployment | null };
15
15
 
16
16
  export async function generateAllYaml(assets: Assets, deployments: deployments): Promise<string> {
17
- const { name, tls, apiToken, path, config } = assets;
17
+ const { name, tls, apiPath, path, config } = assets;
18
18
  const code = await fs.readFile(path);
19
19
  const hash = crypto.createHash("sha256").update(code).digest("hex");
20
20
 
@@ -23,7 +23,7 @@ export async function generateAllYaml(assets: Assets, deployments: deployments):
23
23
  clusterRole(name, assets.capabilities, config.rbacMode, config.rbac),
24
24
  clusterRoleBinding(name),
25
25
  serviceAccount(name),
26
- apiTokenSecret(name, apiToken),
26
+ apiPathSecret(name, apiPath),
27
27
  tlsSecret(name, tls),
28
28
  deployments.default,
29
29
  service(name),
@@ -6,7 +6,7 @@ import { clusterRole } from "../rbac";
6
6
  import { promises as fs } from "fs";
7
7
 
8
8
  type ChartOverrides = {
9
- apiToken: string;
9
+ apiPath: string;
10
10
  capabilities: CapabilityExport[];
11
11
  config: ModuleConfig;
12
12
  hash: string;
@@ -16,7 +16,7 @@ type ChartOverrides = {
16
16
 
17
17
  // Helm Chart overrides file (values.yaml) generated from assets
18
18
  export async function overridesFile(
19
- { hash, name, image, config, apiToken, capabilities }: ChartOverrides,
19
+ { hash, name, image, config, apiPath, capabilities }: ChartOverrides,
20
20
  path: string,
21
21
  imagePullSecrets: string[],
22
22
  ): Promise<void> {
@@ -27,7 +27,7 @@ export async function overridesFile(
27
27
  additionalIgnoredNamespaces: [],
28
28
  rbac: rbacOverrides,
29
29
  secrets: {
30
- apiToken: Buffer.from(apiToken).toString("base64"),
30
+ apiPath: Buffer.from(apiPath).toString("base64"),
31
31
  },
32
32
  hash,
33
33
  namespace: {
@@ -34,8 +34,8 @@ export class Controller {
34
34
  // Metrics collector
35
35
  #metricsCollector = metricsCollector;
36
36
 
37
- // The token used to authenticate requests
38
- #token = "";
37
+ // The path used to authenticate requests
38
+ #path = "";
39
39
 
40
40
  // The express app instance
41
41
  readonly #app = express();
@@ -93,14 +93,14 @@ export class Controller {
93
93
  cert: fs.readFileSync(process.env.SSL_CERT_PATH || "/etc/certs/tls.crt"),
94
94
  };
95
95
 
96
- // Get the API token if not in watch mode
96
+ // Get the API path if not in watch mode
97
97
  if (!isWatchMode()) {
98
- // Get the API token from the environment variable or the mounted secret
99
- this.#token = process.env.PEPR_API_TOKEN || fs.readFileSync("/app/api-token/value").toString().trim();
100
- Log.info(`Using API token: ${this.#token}`);
98
+ // Get the API path from the environment variable or the mounted secret
99
+ this.#path = process.env.PEPR_API_PATH || fs.readFileSync("/app/api-path/value").toString().trim();
100
+ Log.info(`Using API path: ${this.#path}`);
101
101
 
102
- if (!this.#token) {
103
- throw new Error("API token not found");
102
+ if (!this.#path) {
103
+ throw new Error("API path not found");
104
104
  }
105
105
  }
106
106
 
@@ -149,35 +149,35 @@ export class Controller {
149
149
  }
150
150
 
151
151
  // Require auth for webhook endpoints
152
- this.#app.use(["/mutate/:token", "/validate/:token"], this.#validateToken);
152
+ this.#app.use(["/mutate/:path", "/validate/:path"], this.#validatepath);
153
153
 
154
154
  // Mutate endpoint
155
- this.#app.post("/mutate/:token", this.#admissionReq("Mutate"));
155
+ this.#app.post("/mutate/:path", this.#admissionReq("Mutate"));
156
156
 
157
157
  // Validate endpoint
158
- this.#app.post("/validate/:token", this.#admissionReq("Validate"));
158
+ this.#app.post("/validate/:path", this.#admissionReq("Validate"));
159
159
  };
160
160
 
161
161
  /**
162
- * Validate the token in the request path
162
+ * Validate the path in the request path
163
163
  *
164
164
  * @param req The incoming request
165
165
  * @param res The outgoing response
166
166
  * @param next The next middleware function
167
167
  * @returns
168
168
  */
169
- #validateToken = (req: express.Request, res: express.Response, next: NextFunction): void => {
170
- // Validate the token
171
- const { token } = req.params;
172
- if (token !== this.#token) {
173
- const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
169
+ #validatepath = (req: express.Request, res: express.Response, next: NextFunction): void => {
170
+ // Validate the path
171
+ const { path } = req.params;
172
+ if (path !== this.#path) {
173
+ const err = `Unauthorized: invalid path '${path.replace(/[^\w]/g, "_")}'`;
174
174
  Log.info(err);
175
175
  res.status(401).send(err);
176
176
  this.#metricsCollector.alert();
177
177
  return;
178
178
  }
179
179
 
180
- // Token is valid, continue
180
+ // path is valid, continue
181
181
  next();
182
182
  };
183
183
 
@@ -54,7 +54,7 @@ export class MetricsCollector {
54
54
  this.#registry = new Registry();
55
55
  this.#prefix = prefix;
56
56
  this.addCounter(this.#metricNames.errors, "Mutation/Validate errors encountered");
57
- this.addCounter(this.#metricNames.alerts, "Mutation/Validate bad api token received");
57
+ this.addCounter(this.#metricNames.alerts, "Mutation/Validate bad api path received");
58
58
  this.addSummary(this.#metricNames.mutate, "Mutation operation summary");
59
59
  this.addSummary(this.#metricNames.validate, "Validation operation summary");
60
60
  this.addGauge(this.#metricNames.cacheMiss, "Number of cache misses per window", ["window"]);