diff --git a/.changeset/seven-cougars-smash.md b/.changeset/seven-cougars-smash.md new file mode 100644 index 0000000000..5493a724d7 --- /dev/null +++ b/.changeset/seven-cougars-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Make `readTaskScheduleDefinitionFromConfig` properly handle bad inputs diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts index fc2a045614..21f51954b5 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts @@ -76,7 +76,7 @@ describe('readTaskScheduleDefinitionFromConfig', () => { ); }); - it('invalid frequency value', () => { + it('invalid frequency key', () => { const config = new ConfigReader({ frequency: { invalid: 'value', @@ -89,6 +89,19 @@ describe('readTaskScheduleDefinitionFromConfig', () => { ); }); + it('invalid frequency value', () => { + const config = new ConfigReader({ + frequency: { + minutes: 'value', + }, + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Unable to convert config value for key 'frequency.minutes' in 'mock-config' to a number", + ); + }); + it('frequency value with additional invalid prop', () => { const config = new ConfigReader({ frequency: { diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts index 1448a328f8..b31cf68f74 100644 --- a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -15,7 +15,7 @@ */ import { Config } from '@backstage/config'; -import { HumanDuration, JsonObject } from '@backstage/types'; +import { HumanDuration } from '@backstage/types'; import { TaskScheduleDefinition } from './types'; import { Duration } from 'luxon'; @@ -31,29 +31,48 @@ const propsOfHumanDuration = [ ]; function convertToHumanDuration(config: Config, key: string): HumanDuration { - const props = config.getConfig(key).keys(); - if (!props.find(prop => propsOfHumanDuration.includes(prop))) { + // Ensures that the root is an object + const root = config.getConfig(key); + + const result: Record = {}; + let found = false; + for (const prop of propsOfHumanDuration) { + const value = root.getOptionalNumber(prop); + if (value !== undefined) { + result[prop] = value; + found = true; + } + } + + if (!found) { throw new Error( `HumanDuration needs at least one of: ${propsOfHumanDuration}`, ); } - const invalidProps = props.filter( - prop => !propsOfHumanDuration.includes(prop), - ); + const invalidProps = root + .keys() + .filter(prop => !propsOfHumanDuration.includes(prop)); if (invalidProps.length > 0) { throw new Error( `HumanDuration does not contain properties: ${invalidProps}`, ); } - return config.get(key) as HumanDuration; + return result as HumanDuration; } function readDuration(config: Config, key: string): Duration | HumanDuration { - return typeof config.get(key) === 'string' - ? Duration.fromISO(config.getString(key)) - : convertToHumanDuration(config, key); + if (typeof config.get(key) === 'string') { + const value = config.getString(key); + const duration = Duration.fromISO(value); + if (!duration.isValid) { + throw new Error(`Invalid duration: ${value}`); + } + return duration; + } + + return convertToHumanDuration(config, key); } function readCronOrDuration(