Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for scheduling functions #1527

Merged
merged 26 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
acec70c
feat: add support for scheduling functions
rtpascual May 16, 2024
a5d6533
update valid natural language and cron
rtpascual May 17, 2024
afbd6f3
Merge branch 'main' into schedule-functions
rtpascual May 17, 2024
f104c9a
Merge branch 'main' into schedule-functions
rtpascual Jun 11, 2024
4346570
pr feedback and add unit tests
rtpascual Jun 11, 2024
d7f26db
add test that throws when schedule will invoke function again before …
rtpascual Jun 14, 2024
562e275
update schedule rate validation
rtpascual Jun 18, 2024
255dfc5
Merge branch 'main' into schedule-functions
rtpascual Jul 9, 2024
46d8d08
Merge branch 'main' into schedule-functions
rtpascual Jul 10, 2024
3e0bccd
add tests
rtpascual Jul 10, 2024
4f797ec
update e2e test
rtpascual Jul 10, 2024
d86ea22
fix e2e test
rtpascual Jul 10, 2024
8e8063f
PR feedback
rtpascual Jul 15, 2024
760888a
Merge branch 'main' into schedule-functions
rtpascual Jul 15, 2024
79dfa9f
fix test
rtpascual Jul 15, 2024
2d11214
add more testing
rtpascual Jul 16, 2024
3c846fb
fix lint and e2e test
rtpascual Jul 16, 2024
f6821ba
try this
rtpascual Jul 16, 2024
f0311eb
try this
rtpascual Jul 16, 2024
0d7e55a
try that
rtpascual Jul 16, 2024
6b89ceb
try this
rtpascual Jul 16, 2024
46a2192
Merge branch 'main' into schedule-functions
rtpascual Jul 16, 2024
2950c72
Update packages/backend-function/src/factory.ts
rtpascual Jul 17, 2024
0fa4a84
Merge branch 'main' into schedule-functions
rtpascual Jul 17, 2024
28a6825
pr feedback
rtpascual Jul 17, 2024
ca2d0bd
pr feedback
rtpascual Jul 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/green-coins-lie.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@aws-amplify/backend-function': minor
---

add support for scheduling functions
1 change: 1 addition & 0 deletions .eslint_dictionary.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"commonjs",
"corepack",
"cors",
"cron",
"ctor",
"darwin",
"datastore",
Expand Down
10 changes: 10 additions & 0 deletions packages/backend-function/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export type AddEnvironmentFactory = {
addEnvironment: (key: string, value: string | BackendSecret) => void;
};

// @public (undocumented)
export type Cron = `${string} ${string} ${string} ${string} ${string}` | `${string} ${string} ${string} ${string} ${string} ${string}`;

// @public
export const defineFunction: (props?: FunctionProps) => ConstructFactory<ResourceProvider<FunctionResources> & ResourceAccessAcceptorFactory & AddEnvironmentFactory>;

Expand All @@ -26,11 +29,18 @@ export type FunctionProps = {
memoryMB?: number;
environment?: Record<string, string | BackendSecret>;
runtime?: NodeVersion;
schedule?: TimeInterval | TimeInterval[];
};

// @public (undocumented)
export type NodeVersion = 16 | 18 | 20;

// @public (undocumented)
export type Rate = `every ${number}m` | `every ${number}h` | `every day` | `every week` | `every month` | `every year`;
edwardfoyle marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion for alternative namings:

type TimeInterval = `every xx`;
type CronSchedule = `{string} ...`
type Schedule = TimeInterval | CronSchedule

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was avoiding type Schedule because of the naming clash with the Schedule class from aws-cdk-lib/aws-events. Alternatively we can use FunctionSchedule?


// @public (undocumented)
export type TimeInterval = Rate | Cron;

// (No @packageDocumentation comment for this package)

```
53 changes: 53 additions & 0 deletions packages/backend-function/src/factory.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,59 @@ void describe('AmplifyFunctionFactory', () => {
});
});

void describe('schedule property', () => {
void it('sets valid schedule - rate', () => {
const lambda = defineFunction({
entry: './test-assets/default-lambda/handler.ts',
schedule: 'every 5m',
}).getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda.resources.lambda));

template.hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'cron(*/5 * * * ? *)',
});
});

void it('sets valid schedule - cron', () => {
const lambda = defineFunction({
entry: './test-assets/default-lambda/handler.ts',
schedule: '0 1 * * *',
}).getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda.resources.lambda));

template.hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'cron(0 1 * * ? *)',
});
});

void it('sets valid schedule array', () => {
const lambda = defineFunction({
entry: './test-assets/default-lambda/handler.ts',
schedule: ['0 1 * * *', 'every 5m'],
}).getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda.resources.lambda));

template.resourceCountIs('AWS::Events::Rule', 2);

template.hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'cron(0 1 * * ? *)',
});

template.hasResourceProperties('AWS::Events::Rule', {
ScheduleExpression: 'cron(*/5 * * * ? *)',
});
});

void it('defaults to no event rule created', () => {
const lambda = defineFunction({
entry: './test-assets/default-lambda/handler.ts',
}).getInstance(getInstanceProps);
const template = Template.fromStack(Stack.of(lambda.resources.lambda));

template.resourceCountIs('AWS::Events::Rule', 0);
});
});

void describe('resourceAccessAcceptor', () => {
void it('attaches policy to execution role and configures ssm environment context', () => {
const functionFactory = defineFunction({
Expand Down
45 changes: 45 additions & 0 deletions packages/backend-function/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,26 @@ import { FunctionEnvironmentTypeGenerator } from './function_env_type_generator.
import { AttributionMetadataStorage } from '@aws-amplify/backend-output-storage';
import { fileURLToPath } from 'node:url';
import { AmplifyUserError, TagName } from '@aws-amplify/platform-core';
import { ScheduleParser } from './schedule_parser.js';

const functionStackType = 'function-Lambda';

export type AddEnvironmentFactory = {
addEnvironment: (key: string, value: string | BackendSecret) => void;
};

export type Cron =
| `${string} ${string} ${string} ${string} ${string}`
| `${string} ${string} ${string} ${string} ${string} ${string}`;
export type Rate =
| `every ${number}m`
| `every ${number}h`
| `every day`
| `every week`
| `every month`
| `every year`;
export type TimeInterval = Rate | Cron;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Types were done this way because I started getting Expression produces a union type that is too complex to represent Typescript error when I introduced wildcards for cron. Cron type is less strict and we perform our own cron validation to validate what we support (which could change in the future).


/**
* Entry point for defining a function in the Amplify ecosystem
*/
Expand Down Expand Up @@ -93,6 +106,19 @@ export type FunctionProps = {
* Defaults to the oldest NodeJS LTS version. See https://nodejs.org/en/about/previous-releases
*/
runtime?: NodeVersion;

/**
* A time interval string to periodically run the function.
* This can be either a string of `"every <positive whole number><m (minute) or h (hour)>"`, `"every day|week|month|year"` or cron expression.
* Defaults to no scheduling for the function.
* @example
* schedule: "every 5m"
* @example
* schedule: "every week"
* @example
* schedule: "0 9 * * 2" // every Monday at 9am
*/
schedule?: TimeInterval | TimeInterval[];
};

/**
Expand Down Expand Up @@ -137,6 +163,7 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {
memoryMB: this.resolveMemory(),
environment: this.props.environment ?? {},
runtime: this.resolveRuntime(),
schedule: this.resolveSchedule(),
};
};

Expand Down Expand Up @@ -226,6 +253,18 @@ class FunctionFactory implements ConstructFactory<AmplifyFunction> {

return this.props.runtime;
};

private resolveSchedule = () => {
edwardfoyle marked this conversation as resolved.
Show resolved Hide resolved
if (!this.props.schedule) {
return [];
}

const schedules = Array.isArray(this.props.schedule)
awsluja marked this conversation as resolved.
Show resolved Hide resolved
? this.props.schedule
: [this.props.schedule];

return schedules;
};
}

type HydratedFunctionProps = Required<FunctionProps>;
Expand Down Expand Up @@ -335,6 +374,12 @@ class AmplifyFunction
);
}

const schedules = Array.isArray(props.schedule)
? props.schedule
: [props.schedule];

new ScheduleParser(functionLambda, schedules);

Tags.of(functionLambda).add(TagName.FRIENDLY_NAME, id);

this.functionEnvironmentTranslator = new FunctionEnvironmentTranslator(
Expand Down
Loading
Loading