Skip to content

Commit

Permalink
Merge pull request #449 from kubeshop/feat/validation/custom-plugins
Browse files Browse the repository at this point in the history
Feat/validation/custom plugins
  • Loading branch information
monojack authored Jul 12, 2023
2 parents aa0f2b7 + 7314d5c commit 2935949
Show file tree
Hide file tree
Showing 9 changed files with 56 additions and 21 deletions.
5 changes: 5 additions & 0 deletions .changeset/violet-cheetahs-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@monokle/validation": minor
---

load custom plugins using `settings.[plugin-name].pluginUrl`
4 changes: 2 additions & 2 deletions packages/validation/src/MonokleValidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {NSA_TAXONOMY} from './taxonomies/nsa.js';
import {CIS_TAXONOMY} from './taxonomies/cis.js';
import {ResourceParser} from './common/resourceParser.js';

export type PluginLoader = (name: string) => Promise<Plugin>;
export type PluginLoader = (name: string, settings?: Record<string, any>) => Promise<Plugin>;
export type CustomPluginLoader = (name: string, parser: ResourceParser) => Promise<Plugin>;

export function createMonokleValidator(loader: PluginLoader, fallback?: PluginMap) {
Expand Down Expand Up @@ -178,7 +178,7 @@ export class MonokleValidator implements Validator {
const loading = await Promise.allSettled(
missingPlugins.map(async p => {
try {
const validator = await this._loader(p);
const validator = await this._loader(p, config.settings?.[p]);
return validator;
} catch (err) {
const msg = err instanceof Error ? err.message : 'reason unknown';
Expand Down
2 changes: 2 additions & 0 deletions packages/validation/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ export const CORE_PLUGINS = [
'open-policy-agent',
'metadata',
] as const;

export const CUSTOM_PLUGINS_URL_BASE = 'https://plugins.monokle.com/validation';
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {MetadataValidator} from './validators/metadata/validator.js';
import kbpPlugin from './validators/practices/plugin.js';
import pssPlugin from './validators/pod-security-standards/plugin.js';
import {dynamicImportCustomPluginLoader} from './pluginLoaders/dynamicImportLoader.js';
import {CUSTOM_PLUGINS_URL_BASE} from './constants.js';

/**
* Creates a Monokle validator that can dynamically fetch custom plugins.
Expand All @@ -22,7 +23,7 @@ export function createExtensibleMonokleValidator(
schemaLoader: SchemaLoader = new SchemaLoader(),
customPluginLoader: CustomPluginLoader = dynamicImportCustomPluginLoader
) {
return new MonokleValidator(async (pluginName: string) => {
return new MonokleValidator(async (pluginName: string, settings?: Record<string, any>) => {
switch (pluginName) {
case 'pod-security-standards':
return new SimpleCustomValidator(pssPlugin, parser);
Expand All @@ -46,8 +47,18 @@ export function createExtensibleMonokleValidator(
return new DevCustomValidator(parser);
default:
try {
const customPlugin = await customPluginLoader(pluginName, parser);
return customPlugin;
if (settings?.pluginUrl) {
const customPlugin = await import(/* @vite-ignore */ settings.pluginUrl);
return new SimpleCustomValidator(customPlugin.default, parser);
}
if (settings?.ref) {
const customPlugin = await import(
/* @vite-ignore */ `${CUSTOM_PLUGINS_URL_BASE}/${settings.ref}/plugin.js`
);
return new SimpleCustomValidator(customPlugin.default, parser);
}
const validator = await customPluginLoader(pluginName, parser);
return validator;
} catch (err) {
throw new Error(
err instanceof Error ? `plugin_not_found: ${err.message}` : `plugin_not_found: ${String(err)}`
Expand Down
15 changes: 11 additions & 4 deletions packages/validation/src/createExtensibleMonokleValidator.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {bundlePluginCode} from './utils/loadCustomPlugin.node.js';
import kbpPlugin from './validators/practices/plugin.js';
import pssPlugin from './validators/pod-security-standards/plugin.js';
import {requireFromStringCustomPluginLoader} from './pluginLoaders/requireFromStringLoader.node.js';
import {CUSTOM_PLUGINS_URL_BASE} from './constants.js';

/**
* Creates a Monokle validator that can dynamically fetch custom plugins.
Expand All @@ -24,8 +25,8 @@ export function createExtensibleMonokleValidator(
schemaLoader: SchemaLoader = new SchemaLoader(),
customPluginLoader: CustomPluginLoader = requireFromStringCustomPluginLoader
) {
return new MonokleValidator(async (pluginName: string) => {
switch (pluginName) {
return new MonokleValidator(async (pluginNameOrUrl: string, settings?: Record<string, any>) => {
switch (pluginNameOrUrl) {
case 'pod-security-standards':
return new SimpleCustomValidator(pssPlugin, parser);
case 'practices':
Expand All @@ -46,8 +47,14 @@ export function createExtensibleMonokleValidator(
return new MetadataValidator(parser);
default:
try {
const customPlugin = await customPluginLoader(pluginName, parser);
return customPlugin;
let nameOrUrl = pluginNameOrUrl;
if (settings?.pluginUrl) {
nameOrUrl = settings.pluginUrl;
} else if (settings?.ref) {
nameOrUrl = `${CUSTOM_PLUGINS_URL_BASE}/${settings.ref}/plugin.js`;
}
const validator = await customPluginLoader(nameOrUrl, parser);
return validator;
} catch (err) {
throw new Error(
err instanceof Error ? `plugin_not_found: ${err.message}` : `plugin_not_found: ${String(err)}`
Expand Down
3 changes: 2 additions & 1 deletion packages/validation/src/pluginLoaders/dynamicImportLoader.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {CustomPluginLoader} from '../MonokleValidator.js';
import {CUSTOM_PLUGINS_URL_BASE} from '../constants.js';
import {SimpleCustomValidator} from '../validators/custom/simpleValidator.js';

export const dynamicImportCustomPluginLoader: CustomPluginLoader = async (pluginName, parser) => {
const url = `https://plugins.monokle.com/validation/${pluginName}/latest.js`;
const url = `${CUSTOM_PLUGINS_URL_BASE}/${pluginName}/latest.js`;
const customPlugin = await import(/* @vite-ignore */ url);
return new SimpleCustomValidator(customPlugin.default, parser);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {CustomPluginLoader} from '../MonokleValidator.js';
import {loadCustomPlugin} from '../utils/loadCustomPlugin.node.js';
import {SimpleCustomValidator} from '../validators/custom/simpleValidator.js';

export const requireFromStringCustomPluginLoader: CustomPluginLoader = async (pluginName, parser) => {
const customPlugin = await loadCustomPlugin(pluginName);
export const requireFromStringCustomPluginLoader: CustomPluginLoader = async (pluginNameOrUrl, parser) => {
const customPlugin = await loadCustomPlugin(pluginNameOrUrl);
return new SimpleCustomValidator(customPlugin, parser);
};
24 changes: 16 additions & 8 deletions packages/validation/src/utils/loadCustomPlugin.node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import fetch from 'isomorphic-fetch';
import requireFromString from 'require-from-string';
import virtual from '@rollup/plugin-virtual';
import {rollup} from 'rollup';
import {CUSTOM_PLUGINS_URL_BASE} from '../constants';

export async function bundlePluginCode(code: string) {
const bundle = await rollup({
Expand All @@ -19,21 +20,28 @@ export async function bundlePluginCode(code: string) {
return output[0].code;
}

export async function fetchCustomPluginCode(pluginName: string) {
const url = `https://plugins.monokle.com/validation/${pluginName}/latest.js`;
export async function fetchCustomPluginCode(pluginName: string, pluginUrl?: string) {
const url = pluginUrl ?? `${CUSTOM_PLUGINS_URL_BASE}/${pluginName}/latest.js`;
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Error fetching ${url}: ${response.statusText}`);
}
return response.text();
}

export async function loadCustomPlugin(pluginName: string) {
const filePath = path.join(process.cwd(), '.monokle-plugins', `${pluginName}-plugin.js`);

const pluginCode = fs.existsSync(filePath)
? fs.readFileSync(filePath, {encoding: 'utf-8'})
: await fetchCustomPluginCode(pluginName);
export async function loadCustomPlugin(pluginNameOrUrl: string) {
let pluginCode: string;
let filePath: string;
try {
const urlObj = new URL(pluginNameOrUrl);
filePath = urlObj.pathname;
pluginCode = await fetchCustomPluginCode('custom-plugin', pluginNameOrUrl);
} catch (error) {
filePath = path.join(process.cwd(), '.monokle-plugins', `${pluginNameOrUrl}-plugin.js`);
pluginCode = fs.existsSync(filePath)
? fs.readFileSync(filePath, {encoding: 'utf-8'})
: await fetchCustomPluginCode(pluginNameOrUrl);
}

const code = await bundlePluginCode(pluginCode);
const customPlugin = requireFromString(code, filePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import {LoadedPolicy, OpaProperties, PolicyError} from './types.js';
import {WasmLoader} from '../../wasmLoader/WasmLoader.js';
import {isKustomizationResource} from '../../references/utils/kustomizeRefs.js';
import invariant from '../../utils/invariant.js';
import {CUSTOM_PLUGINS_URL_BASE} from '../../constants.js';

type Settings = z.infer<typeof Settings>;

const Settings = z.object({
wasmSrc: z.string().default('https://plugins.monokle.com/validation/open-policy-agent/trivy.wasm'),
wasmSrc: z.string().default(`${CUSTOM_PLUGINS_URL_BASE}/open-policy-agent/trivy.wasm`),
});

const CONTROLLER_KINDS = ['Deployment', 'StatefulSet', 'Job', 'DaemonSet', 'ReplicaSet', 'ReplicationController'];
Expand Down

0 comments on commit 2935949

Please sign in to comment.