Skip to content

Commit

Permalink
feat: add typegen for known core resources
Browse files Browse the repository at this point in the history
  • Loading branch information
WitoDelnat committed Nov 23, 2022
1 parent 62ca8ec commit 8c476f9
Show file tree
Hide file tree
Showing 12 changed files with 142 additions and 44 deletions.
5 changes: 5 additions & 0 deletions .changeset/eight-games-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"create-monokle-plugin": minor
---

Add typegen for known core resources
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
build
dist
node_modules
src/crds/__generated__/**
**/__generated__/**
internal/server.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,22 @@ import { parseAllDocuments } from "yaml";
import { compile } from "json-schema-to-typescript";
import klaw from "klaw-sync";

const CRD_DIR = "./src/crds";
const CRD_DIR = "./src/schemas/crds";

await generateKnownCoreResourceTypes();
await generateCustomResourceTypes();

async function generateCustomResourceTypes() {
if (!fse.existsSync(CRD_DIR)) {
return;
}

if (fse.existsSync(CRD_DIR)) {
const files = klaw(CRD_DIR, { nodir: true, depthLimit: 0 });

for (const file of files) {
const crds = await fse.readFile(file.path, "utf8");
const docs = parseAllDocuments(crds);

for (const doc of docs) {
const resource = doc.toJS();

Expand All @@ -27,14 +36,62 @@ if (fse.existsSync(CRD_DIR)) {
});

const code = `${types}
export function is${kind}(resource: unknown): resource is ${kind} {
return typeof resource === "object" && (resource as any)?.apiVersion === "${apiVersion}" && (resource as any)?.kind === "${kind}";
}
`;
export function is${kind}(resource: unknown): resource is ${kind} {
return typeof resource === "object" && (resource as any)?.apiVersion === "${apiVersion}" && (resource as any)?.kind === "${kind}";
}
`;

await fse.outputFile(`src/crds/__generated__/${name}.ts`, code);
await fse.outputFile(`src/schemas/__generated__/${name}.ts`, code);
}
}
}

async function generateKnownCoreResourceTypes() {
const KNOWN_KINDS = [
["ClusterRole", "rbac/v1", "rbac.authorization.k8s.io/v1"],
["ClusterRoleBinding", "rbac/v1", "rbac.authorization.k8s.io/v1"],
["CronJob", "batch/v1", "batch/v1"],
["ConfigMap", "core/v1", "v1"],
["CustomResourceDefinition", "apiextensions/v1", "apiextensions.k8s.io/v1"],
["DaemonSet", "apps/v1", "apps/v1"],
["Deployment", "apps/v1", "apps/v1"],
["Endpoints", "core/v1", "v1"],
["EndpointSlice", "discovery/v1", "discovery.k8s.io/v1"],
["HorizontalPodAutoscaler", "autoscaling/v2", "autoscaling/v2"],
["Ingress", "networking/v1", "networking.k8s.io/v1"],
["Job", "batch/v1", "batch/v1"],
["LimitRange", "core/v1", "v1"],
["Namespace", "core/v1", "v1"],
["NetworkPolicy", "networking/v1", "networking.k8s.io/v1"],
["PersistentVolume", "core/v1", "v1"],
["PersistentVolumeClaim", "core/v1", "v1"],
["Pod", "core/v1", "v1"],
["ReplicaSet", "apps/v1", "apps/v1"],
["ReplicationController", "core/v1", "v1"],
["ResourceQuota", "core/v1", "v1"],
["Role", "rbac/v1", "rbac.authorization.k8s.io/v1"],
["RoleBinding", "rbac/v1", "rbac.authorization.k8s.io/v1"],
["Secret", "core/v1", "v1"],
["Service", "core/v1", "v1"],
["ServiceAccount", "core/v1", "v1"],
["StatefulSet", "apps/v1", "apps/v1"],
["StorageClass", "storage/v1", "storage.k8s.io/v1"],
["VolumeAttachment", "storage/v1", "storage.k8s.io/v1"],
];

for (const [kind, importVersion, apiVersion] of KNOWN_KINDS) {
const code = `import type { ${kind} } from "kubernetes-types/${importVersion}.d.js";
export type { ${kind} } from "kubernetes-types/${importVersion}.d.js";
export function is${kind}(resource: unknown): resource is ${kind} {
return typeof resource === "object" && (resource as any)?.apiVersion === "${apiVersion}" && (resource as any)?.kind === "${kind}";
}
`;

const name = `${kind.toLowerCase()}.${apiVersion.replace("/", ".")}`;
await fse.outputFile(`src/schemas/__generated__/${name}.ts`, code);
}
}

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

Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"cors": "2.8.5",
"esbuild": "0.15.12",
"fs-extra": "10.1.0",
"kubernetes-types": "1.25.0",
"json-schema-to-typescript": "11.0.2",
"klaw-sync": "6.0.0",
"openapi-typescript": "5.4.1",
Expand All @@ -42,6 +43,6 @@
"yaml": "2.1.3"
},
"dependencies": {
"@monokle/validation": "0.7.1"
"@monokle/validation": "0.9.1"
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { definePlugin } from "@monokle/validation/custom";
import { noEmptyAnnotations } from "./rules/noEmptyAnnotations.js";
import { noExternalUrl } from "./rules/noExternalUrl.js";
import { sealedSecrets } from "./rules/sealedSecrets.js";
import { noPortMismatch } from "./rules/noPortMismatch.js";

export default definePlugin({
id: "YCP",
name: "Your custom plugin",
description: "Welcome to your first plugin!",
rules: {
noEmptyAnnotations,
sealedSecrets,
noPortMismatch,
noExternalUrl,
},
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { defineRule } from "@monokle/validation/custom";

/**
* A basic example.
*/
export const noEmptyAnnotations = defineRule({
id: "YCP001",
id: 1,
description: "Require annotations as metadata.",
help: "Add any annotation to the Kubernetes resource.",
validate({ resources }, { report }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { defineRule } from "@monokle/validation/custom";
import { isPrometheus } from "../crds/__generated__/prometheus.monitoring.coreos.com.v1.js";
import { isPrometheus } from "../schemas/__generated__/prometheus.monitoring.coreos.com.v1.js";

/**
* Example with a CRD.
*
* @remark use `npm run codegen` to build the types
*/
export const noExternalUrl = defineRule({
id: "YCP005",
id: 3,
description: "Disallow external URLs for Prometheus instances.",
help: "Remove the externalUrl.",
validate({ resources }, { report }) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { defineRule } from "@monokle/validation/custom";
import { isDeployment } from "../schemas/__generated__/deployment.apps.v1.js";
import { isService } from "../schemas/__generated__/service.v1.js";

/**
* An advanced example which uses related resources
*
* @remark use `npm run codegen` to generate types.
*/
export const noPortMismatch = defineRule({
id: 2,
description: "The target port should match any container port.",
help: "Change to target port to a port that matching a container's port.",
validate({ resources }, { getRelated, report }) {
resources.filter(isService).forEach((service) => {
const deployments = getRelated(service).filter(isDeployment);

const validPorts = deployments.flatMap((d) =>
d.spec?.template.spec?.containers.flatMap((c) =>
c.ports?.flatMap((p) => p.containerPort)
)
);

const servicePorts = service.spec?.ports ?? [];
servicePorts.forEach((port, index: number) => {
if (!validPorts.includes(Number(port.targetPort))) {
report(service, { path: `spec.ports.${index}.targetPort` });
}
});
});
},
});

This file was deleted.

4 changes: 1 addition & 3 deletions packages/validation/src/validators/custom/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,8 @@ export type RuleInit = {

/**
* The Kubernetes resource.
*
* @remark You can use
*
* @remark you can generate helpers for your resources and CRDs. Learn more in the README.
* @remark Ue `npm run codegen` to generate types. Learn more in the README.
* @example `isDeployment(resource)` => resource.spec is fully typed.
*/
export type Resource = any;
Expand Down

0 comments on commit 8c476f9

Please sign in to comment.