Skip to content

Commit

Permalink
Merge branch 'main' into 915
Browse files Browse the repository at this point in the history
  • Loading branch information
cmwylie19 committed Jul 17, 2024
2 parents a5272df + 98fca3e commit 9c964b6
Show file tree
Hide file tree
Showing 8 changed files with 386 additions and 22 deletions.
138 changes: 137 additions & 1 deletion docs/060_best-practices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,143 @@ The workflow for developing features in Pepr is:

## Debugging

Pepr can be broken down into two parts: Admission and Watches. If the focus of the debug is on a Mutation or Validation, then only pay attention to pods with labels `pepr.dev/controller: admission`, else, you can focus on `pepr.dev/controller: watch`.
- [Debugging During Module Development](https://docs.pepr.dev/main/best-practices/#debugging-during-module-development)
- [Logging](https://docs.pepr.dev/main/best-practices/#logging)
- [Internal Error Occurred](https://docs.pepr.dev/main/best-practices/#internal-error-occurred)
- [Pepr Store](https://docs.pepr.dev/main/best-practices/#pepr-store)


Welcome to the the debugging section! 🐛

Pepr is composed of `Modules` (ie, what happens when you issue `npx pepr init`), [Capabilities](https://docs.pepr.dev/main/user-guide/capabilities/) like `hello-pepr.ts`, and [Actions](https://docs.pepr.dev/main/user-guide/actions/) (ie, the blocks of code containing filters and `Mutate`, `Validate`, `Watch`, `Reconcile`, `OnSchedule`). You can have as many Capabilities as you would like in a Module.

Pepr is a webhook-based system, meaning it is event-driven. When a resource is created, updated, or deleted, Pepr is called to perform the actions you have defined in your Capabilities. It's common for multiple webhooks to exist in a cluster, not just Pepr. When there are multiple webhooks, the order in which they are called is not guaranteed. The only guarantee is that all of the `MutatingWebhooks` will be called before all of the `ValidatingWebhooks`. After the admission webhooks are called, the `Watch` and `Reconcile` are called. The `Reconcile` and `Watch` create a watch on the resources specified in the `When` block and are watched for changes after admission. The difference between reconcile and watch is that `Reconcile` processes events in a queue to guarantee that the events are processed in order where as watch does not.

Considering that many webhooks may be modifying the same resource, it is best practice to validate the resource after mutations are made to ensure that the resource is in a valid state if it has been changed since the last mutation.



```typescript
When(a.Pod)
.IsCreated()
.InNamespace("my-app")
.WithName("database")
.Mutate(pod => {
pod.metadata.labels["pepr"] = "true";
return pod;
})
// another mutating webhook could removed labels
.Validate(pod => {
if (pod.metadata.labels["pepr"] !== "true") {
return pod.Approve("Label 'pepr' must be 'true'");
}
return pod.Deny("Needs pepr label set to true")
});
```

_If you think your Webhook is not being called for a given resource, check the `*WebhookConfiguration`._


### Debugging During Module Development

Pepr supports breakpoints in the VSCode editor. To use breakpoints, run `npx pepr dev` in the root of a Pepr module using a JavaScript Debug Terminal. This command starts the Pepr development server running at `localhost:3000` with the `*WebhookConfiguration` configured to send `AdmissionRequest` objects to the local address.

This allows you to set breakpoints in `Mutate()`, `Validate()`, `Reconcile()`, `Watch()` or `OnSchedule()` and step through module code.

Note that you will need a cluster running:

```bash
k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait
```

```typescript
When(a.Pod)
.IsCreated()
.InNamespace("my-app")
.WithName("database")
.Mutate(pod => {
// Set a breakpoint here
pod.metadata.labels["pepr"] = "true";
return pod;
})
.Validate(pod => {
// Set a breakpoint here
if (pod.metadata.labels["pepr"] !== "true") {
return ["Label 'pepr' must be 'true'"];
}
});
```

### Logging

Pepr can deploy two types of controllers: Admission and Watch. The controllers deployed are dictated by the [Actions](https://docs.pepr.dev/main/user-guide/actions/) called for by a given set of Capabilities (Pepr only deploys what is necessary). Within those controllers, the default log level is `info` but that can be changed to `debug` by setting the `LOG_LEVEL` environment variable to `debug`.

To pull logs for all controller pods:

```bash
kubectl logs -l app -n pepr-system
```

#### Admission Controller

If the focus of the debug is on a `Mutate()` or `Validate()`, the relevenat logs will be from pods with label `pepr.dev/controller: admission`.

```bash
kubectl logs -l pepr.dev/controller=admission -n pepr-system
```

More refined admission logs -- which can be optionally filtered by the module UUID -- can be obtained with [`npx pepr monitor`](https://docs.pepr.dev/main/best-practices/#monitoring)

```bash
npx pepr monitor
```

#### Watch Controller

If the focus of the debug is a `Watch()`, `Reconcile()`, or `OnSchedule()`, look for logs from pods containing label `pepr.dev/controller: watcher`.

```bash
kubectl logs -l pepr.dev/controller=watcher -n pepr-system
```

### Internal Error Occurred

```bash
Error from server (InternalError): Internal error occurred: failed calling webhook "<pepr_module>pepr.dev": failed to call webhook: Post ...
```

When an internal error occurs, check the deployed `*WebhookConfiguration` resources' timeout and failurePolicy configurations. If the failurePolicy is set to `Fail`, and a request cannot be processed within the timeout, that request will be rejected. If the failurePolicy is set to `Ignore`, given the same timeout conditions, the request will (perhaps surprisingly) be allowed to continue.

If you have a validating webhook, the recommended is to set the failurePolicy to `Fail` to ensure that the request is rejected if the webhook fails.

```yaml
failurePolicy: Fail
matchPolicy: Equivalent
timeoutSeconds: 3
```
The failurePolicy and timeouts can be set in the Module's `package.json` file, under the `pepr` configuration key. If changed, the settings will be reflected in the `*WebhookConfiguration` after the next build:

```json
"pepr": {
"uuid": "static-test",
"onError": "ignore",
"webhookTimeout": 10,
}
```

Read more on customization [here](https://docs.pepr.dev/main/user-guide/customization/).


### Pepr Store

If you need to read all store keys, or you think the PeprStore is malfunctioning, you can check the PeprStore CR:

```bash
kubectl get peprstore -n pepr-system -o yaml
```

You should run in `npx pepr dev` mode to debug the issue, see the [Debugging During Module Development](https://docs.pepr.dev/main/best-practices/#debugging-during-module-development) section for more information.

## Deployment

Expand Down
13 changes: 13 additions & 0 deletions docs/080_faq/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,19 @@
# Frequently Asked Questions


## How do I remove the punycode warning?

```bash
export NODE_OPTIONS="--disable-warning=DEP0040"
```

or

```bash
npx --node-options="--disable-warning=DEP0040" pepr [command]
```


## How do I add custom labels to Pepr's Kubernetes manifests?

During the build process, custom labels can be added the Kubernetes manifests that Pepr generates based on the Pepr section of the `package.json`. Currently, adding custom labels to `namespace` is supported.
Expand Down
75 changes: 71 additions & 4 deletions journey/pepr-build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { execSync } from "child_process";
import { promises as fs } from "fs";
import { resolve } from "path";
import { V1ObjectMeta, KubernetesObject } from '@kubernetes/client-node';
import yaml from "js-yaml";
import { cwd } from "./entrypoint.test";

export function peprBuild() {
Expand All @@ -23,6 +24,72 @@ export function peprBuild() {
await fs.access(resolve(cwd, "dist", "zarf.yaml"));
await validateZarfYaml();
});

it("should correct merge in the package.json env vars into the values.yaml helm chart file", async () => {
interface ValuesJSON {
admission: {
env: Record<string, string>[] | undefined;
};
watcher: {
env: Record<string, string>[] | undefined;
};
}

const expectedWatcherEnv = [
{
"name": "PEPR_WATCH_MODE",
"value": "true"
},
{
"name": "PEPR_PRETTY_LOG",
"value": "false"
},
{
"name": "LOG_LEVEL",
"value": "info"
},
{
"name": "MY_CUSTOM_VAR",
"value": "example-value"
},
{
"name": "ZARF_VAR",
"value": "###ZARF_VAR_THING###"
}
];

const expectedAdmissionEnv = [
{
"name": "PEPR_WATCH_MODE",
"value": "false"
},
{
"name": "PEPR_PRETTY_LOG",
"value": "false"
},
{
"name": "LOG_LEVEL",
"value": "info"
},
{
"name": "MY_CUSTOM_VAR",
"value": "example-value"
},
{
"name": "ZARF_VAR",
"value": "###ZARF_VAR_THING###"
}
]

try {
const valuesYaml = await fs.readFile(resolve(cwd, "dist", "static-test-chart", "values.yaml"), "utf8");
const valuesJSON = yaml.load(valuesYaml) as ValuesJSON;
expect(valuesJSON.admission.env).toEqual(expectedAdmissionEnv);
expect(valuesJSON.watcher!.env).toEqual(expectedWatcherEnv);
} catch (error) {
expect(error).toBeUndefined();
}
})
}

async function validateHelmChart() {
Expand Down Expand Up @@ -102,9 +169,9 @@ function parseYAMLToJSON(yamlContent: string): KubernetesObject[] | null {

function sortKubernetesObjects(objects: KubernetesObject[]): KubernetesObject[] {
return objects.sort((a, b) => {
if (a?.kind !== b?.kind) {
return (a?.kind ?? '').localeCompare(b?.kind ?? '');
}
return ((a && a.metadata && (a.metadata as V1ObjectMeta)?.name) ?? '').localeCompare((b && b.metadata && (b.metadata as V1ObjectMeta)?.name) ?? '');
if (a?.kind !== b?.kind) {
return (a?.kind ?? '').localeCompare(b?.kind ?? '');
}
return ((a && a.metadata && (a.metadata as V1ObjectMeta)?.name) ?? '').localeCompare((b && b.metadata && (b.metadata as V1ObjectMeta)?.name) ?? '');
});
}
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"@types/uuid": "10.0.0",
"fast-check": "^3.19.0",
"jest": "29.7.0",
"js-yaml": "^4.1.0",
"nock": "13.5.4",
"ts-jest": "29.2.2"
},
Expand Down
31 changes: 14 additions & 17 deletions src/lib/assets/yaml.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,26 @@
import { dumpYaml } from "@kubernetes/client-node";
import crypto from "crypto";
import { promises as fs } from "fs";

import { Assets } from ".";
import { apiTokenSecret, service, tlsSecret, watcherService } from "./networking";
import { deployment, moduleSecret, namespace, watcher } from "./pods";
import { clusterRole, clusterRoleBinding, serviceAccount, storeRole, storeRoleBinding } from "./rbac";
import { webhookConfig } from "./webhooks";
import { mergePkgJSONEnv, envMapToArray } from "../helpers";

// Helm Chart overrides file (values.yaml) generated from assets
export async function overridesFile({ hash, name, image, config, apiToken }: Assets, path: string) {
const pkgJSONAdmissionEnv = {
PEPR_WATCH_MODE: "false",
PEPR_PRETTY_LOG: "false",
LOG_LEVEL: "info",
};
const pkgJSONWatchEnv = {
PEPR_WATCH_MODE: "true",
PEPR_PRETTY_LOG: "false",
LOG_LEVEL: "info",
};

const overrides = {
secrets: {
apiToken: Buffer.from(apiToken).toString("base64"),
Expand All @@ -29,11 +40,7 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
terminationGracePeriodSeconds: 5,
failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
webhookTimeout: config.webhookTimeout,
env: [
{ name: "PEPR_WATCH_MODE", value: "false" },
{ name: "PEPR_PRETTY_LOG", value: "false" },
{ name: "LOG_LEVEL", value: "info" },
],
env: envMapToArray(mergePkgJSONEnv(pkgJSONAdmissionEnv, config.env)),
image,
annotations: {
"pepr.dev/description": `${config.description}` || "",
Expand Down Expand Up @@ -77,11 +84,7 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
},
watcher: {
terminationGracePeriodSeconds: 5,
env: [
{ name: "PEPR_WATCH_MODE", value: "true" },
{ name: "PEPR_PRETTY_LOG", value: "false" },
{ name: "LOG_LEVEL", value: "info" },
],
env: envMapToArray(mergePkgJSONEnv(pkgJSONWatchEnv, config.env)),
image,
annotations: {
"pepr.dev/description": `${config.description}` || "",
Expand Down Expand Up @@ -124,12 +127,6 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
podAnnotations: {},
},
};
if (process.env.PEPR_MODE === "dev") {
overrides.admission.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
overrides.watcher.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
overrides.admission.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
overrides.watcher.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
}

await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
}
Expand Down
Loading

0 comments on commit 9c964b6

Please sign in to comment.