Skip to content

Commit d281577

Browse files
committed
fix(ui): Prefer fixed plugin option values over values from last run
If a plugin options is set to a fixed value in a plugin template, that value is enforced by the backend: If a different value for that option is sent to the API, the run request is rejected. This could cause issues when starting a run with "rerun" and plugin templates were changed in the meantime, because a plugin option used for the last run may now not be allowed anymore. To fix this, always prefer fixed plugin option values over value used by the last run. Signed-off-by: Martin Nonnenmacher <martin.nonnenmacher@doubleopen.io>
1 parent dbfbc94 commit d281577

2 files changed

Lines changed: 259 additions & 6 deletions

File tree

ui/src/routes/organizations/$orgId/products/$productId/repositories/$repoId/_repo-layout/create-run/-components/default-values.ts

Lines changed: 163 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,8 @@ export function defaultValues(
239239
baseDefaults.jobConfigs.advisor.advisors,
240240
config: mergePluginConfigs(
241241
ortRun?.jobConfigs?.advisor?.config,
242-
advisorPluginDefaultValues
242+
advisorPluginDefaultValues,
243+
advisorPlugins
243244
),
244245
keepAliveWorker:
245246
(ortRun.jobConfigs.advisor?.keepAliveWorker && isSuperuser) ||
@@ -265,7 +266,8 @@ export function defaultValues(
265266
),
266267
config: mergePluginConfigs(
267268
ortRun.jobConfigs.scanner?.config,
268-
scannerPluginDefaultValues
269+
scannerPluginDefaultValues,
270+
scannerPlugins
269271
),
270272
},
271273
evaluator: {
@@ -388,4 +390,163 @@ if (import.meta.vitest) {
388390
},
389391
});
390392
});
393+
394+
it('applies fixed plugin option precedence for advisor and scanner reruns', () => {
395+
const ortRun = {
396+
revision: 'main',
397+
path: '',
398+
jobConfigs: {
399+
advisor: {
400+
advisors: ['OSV'],
401+
config: {
402+
OSV: {
403+
options: {
404+
url: 'https://rerun.example/advisor',
405+
fixedNoDefault: 'legacy-advisor-fixed',
406+
token: 'legacy-advisor-token',
407+
},
408+
secrets: {
409+
fixedSecret: 'legacy-advisor-secret',
410+
},
411+
},
412+
},
413+
},
414+
scanner: {
415+
scanners: ['SCANOSS'],
416+
config: {
417+
SCANOSS: {
418+
options: {
419+
url: 'https://rerun.example/scanner',
420+
fixedNoDefault: 'legacy-scanner-fixed',
421+
token: 'legacy-scanner-token',
422+
},
423+
secrets: {
424+
fixedSecret: 'legacy-scanner-secret',
425+
},
426+
},
427+
},
428+
},
429+
},
430+
labels: {},
431+
} as unknown as OrtRun;
432+
433+
const advisorPlugins: PreconfiguredPluginDescriptor[] = [
434+
{
435+
id: 'OSV',
436+
type: 'ADVISOR',
437+
displayName: 'OSV',
438+
summary: 'OSV advisor.',
439+
description: 'OSV advisor.',
440+
options: [
441+
{
442+
name: 'url',
443+
description: 'Base URL.',
444+
type: 'STRING',
445+
defaultValue: 'https://default.example/advisor',
446+
isFixed: true,
447+
isNullable: false,
448+
isRequired: true,
449+
},
450+
{
451+
name: 'fixedNoDefault',
452+
description: 'Fixed without default.',
453+
type: 'STRING',
454+
isFixed: true,
455+
isNullable: false,
456+
isRequired: false,
457+
},
458+
{
459+
name: 'fixedSecret',
460+
description: 'Fixed secret option.',
461+
type: 'SECRET',
462+
isFixed: true,
463+
isNullable: false,
464+
isRequired: false,
465+
},
466+
{
467+
name: 'token',
468+
description: 'Editable token.',
469+
type: 'STRING',
470+
defaultValue: 'default-token',
471+
isFixed: false,
472+
isNullable: false,
473+
isRequired: false,
474+
},
475+
],
476+
},
477+
];
478+
479+
const scannerPlugins: PreconfiguredPluginDescriptor[] = [
480+
{
481+
id: 'SCANOSS',
482+
type: 'SCANNER',
483+
displayName: 'SCANOSS',
484+
summary: 'SCANOSS scanner.',
485+
description: 'SCANOSS scanner.',
486+
options: [
487+
{
488+
name: 'url',
489+
description: 'Base URL.',
490+
type: 'STRING',
491+
defaultValue: 'https://default.example/scanner',
492+
isFixed: true,
493+
isNullable: false,
494+
isRequired: true,
495+
},
496+
{
497+
name: 'fixedNoDefault',
498+
description: 'Fixed without default.',
499+
type: 'STRING',
500+
isFixed: true,
501+
isNullable: false,
502+
isRequired: false,
503+
},
504+
{
505+
name: 'fixedSecret',
506+
description: 'Fixed secret option.',
507+
type: 'SECRET',
508+
isFixed: true,
509+
isNullable: false,
510+
isRequired: false,
511+
},
512+
{
513+
name: 'token',
514+
description: 'Editable token.',
515+
type: 'STRING',
516+
defaultValue: 'default-token',
517+
isFixed: false,
518+
isNullable: false,
519+
isRequired: false,
520+
},
521+
],
522+
},
523+
];
524+
525+
const defaults = defaultValues(
526+
ortRun,
527+
advisorPlugins,
528+
scannerPlugins,
529+
false
530+
);
531+
532+
expect(defaults.jobConfigs.advisor.config).toEqual({
533+
OSV: {
534+
options: {
535+
url: 'https://default.example/advisor',
536+
token: 'legacy-advisor-token',
537+
},
538+
secrets: {},
539+
},
540+
});
541+
542+
expect(defaults.jobConfigs.scanner.config).toEqual({
543+
SCANOSS: {
544+
options: {
545+
url: 'https://default.example/scanner',
546+
token: 'legacy-scanner-token',
547+
},
548+
secrets: {},
549+
},
550+
});
551+
});
391552
}

ui/src/routes/organizations/$orgId/products/$productId/repositories/$repoId/_repo-layout/create-run/-components/plugin-utils.ts

Lines changed: 96 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -134,20 +134,24 @@ export const createPluginConfigSchema = (
134134
};
135135

136136
/**
137-
* Merge the plugin configs from the last run with the default plugin configs. The configs from the last run take
138-
* precedence.
137+
* Merge the plugin configs from the last run with the default plugin configs.
138+
* For fixed options, values configured via plugin descriptors take precedence.
139+
* For non-fixed options, values from the last run take precedence.
139140
*/
140141
export function mergePluginConfigs(
141142
lastRunConfig: { [p: string]: PluginConfig } | null | undefined,
142-
defaultConfig: Record<string, PluginConfig>
143+
defaultConfig: Record<string, PluginConfig>,
144+
plugins: PreconfiguredPluginDescriptor[]
143145
): Record<string, PluginConfig> {
144146
const merged: Record<string, PluginConfig> = {};
147+
const pluginById = new Map(plugins.map((plugin) => [plugin.id, plugin]));
145148

146149
for (const pluginId of Object.keys(defaultConfig)) {
147150
const defaultPlugin = defaultConfig[pluginId];
148151
const ortPlugin = lastRunConfig?.[pluginId];
152+
const pluginDescriptor = pluginById.get(pluginId);
149153

150-
merged[pluginId] = {
154+
const mergedPlugin: PluginConfig = {
151155
options: {
152156
...(defaultPlugin?.options ?? {}),
153157
...(ortPlugin?.options ?? {}),
@@ -157,6 +161,21 @@ export function mergePluginConfigs(
157161
...(ortPlugin?.secrets ?? {}),
158162
},
159163
};
164+
165+
for (const option of pluginDescriptor?.options ?? []) {
166+
if (!option.isFixed) continue;
167+
168+
const section = option.type === 'SECRET' ? 'secrets' : 'options';
169+
const defaultSection = defaultPlugin?.[section] ?? {};
170+
171+
if (Object.prototype.hasOwnProperty.call(defaultSection, option.name)) {
172+
mergedPlugin[section][option.name] = defaultSection[option.name];
173+
} else {
174+
delete mergedPlugin[section][option.name];
175+
}
176+
}
177+
178+
merged[pluginId] = mergedPlugin;
160179
}
161180

162181
if (lastRunConfig) {
@@ -397,4 +416,77 @@ if (import.meta.vitest) {
397416
},
398417
});
399418
});
419+
420+
it('mergePluginConfigs enforces fixed option precedence and clears missing fixed defaults', () => {
421+
const plugins: PreconfiguredPluginDescriptor[] = [
422+
{
423+
id: 'SCANOSS',
424+
type: 'SCANNER',
425+
displayName: 'SCANOSS',
426+
summary: 'A scanner plugin.',
427+
description: 'A scanner plugin.',
428+
options: [
429+
{
430+
name: 'url',
431+
description: 'The API URL.',
432+
type: 'STRING',
433+
defaultValue: 'https://scanner.example/api',
434+
isFixed: true,
435+
isNullable: false,
436+
isRequired: false,
437+
},
438+
{
439+
name: 'fixedNoDefault',
440+
description: 'A fixed option without default.',
441+
type: 'STRING',
442+
isFixed: true,
443+
isNullable: false,
444+
isRequired: false,
445+
},
446+
{
447+
name: 'fixedSecret',
448+
description: 'A fixed secret option.',
449+
type: 'SECRET',
450+
isFixed: true,
451+
isNullable: false,
452+
isRequired: false,
453+
},
454+
{
455+
name: 'timeout',
456+
description: 'Timeout in seconds.',
457+
type: 'INTEGER',
458+
defaultValue: '30',
459+
isFixed: false,
460+
isNullable: false,
461+
isRequired: false,
462+
},
463+
],
464+
},
465+
];
466+
467+
const merged = mergePluginConfigs(
468+
{
469+
SCANOSS: {
470+
options: {
471+
url: 'https://old.example/api',
472+
fixedNoDefault: 'legacy-value',
473+
timeout: '90',
474+
},
475+
secrets: {
476+
fixedSecret: 'legacy-secret',
477+
},
478+
},
479+
},
480+
getPluginDefaultValues(plugins),
481+
plugins
482+
);
483+
484+
expect(merged.SCANOSS).toEqual({
485+
options: {
486+
url: 'https://scanner.example/api',
487+
timeout: '90',
488+
},
489+
secrets: {},
490+
});
491+
});
400492
}

0 commit comments

Comments
 (0)