1
0
Fork 0
mirror of synced 2024-06-01 10:29:48 +12:00

Functions work in progress

This commit is contained in:
Eldad Fux 2020-05-13 01:00:48 +03:00
parent 8b878c87ff
commit 1d4a6cb99e
6 changed files with 225 additions and 57 deletions

View file

@ -11,6 +11,7 @@ use Utopia\Validator\Assoc;
use Utopia\Validator\Text;
use Utopia\Validator\Range;
use Utopia\Validator\WhiteList;
use Cron\CronExpression;
include_once __DIR__ . '/../shared/api.php';
@ -23,12 +24,11 @@ $utopia->post('/v1/functions')
->label('sdk.description', '/docs/references/functions/create-function.md')
->param('name', '', function () { return new Text(128); }, 'Function name.')
->param('vars', [], function () { return new Assoc();}, 'Key-value JSON object.', true)
->param('trigger', 'event', function () { return new WhiteList(['event', 'scheudle']); }, 'Function trigger type.', true)
->param('events', [], function () { return new ArrayList(new Text(256)); }, 'Events list.', true)
->param('schedule', '', function () { return new Cron(); }, 'Schedule CRON syntax.', true)
->param('timeout', 15, function () { return new WhiteList([5, 15, 30, 60]); }, 'Function maximum execution time in seconds.', true)
->param('timeout', 15, function () { return new Range(0, 60); }, 'Function maximum execution time in seconds.', true)
->action(
function ($name, $vars, $trigger, $events, $schedule, $timeout) use ($response, $projectDB) {
function ($name, $vars, $events, $schedule, $timeout) use ($response, $projectDB) {
$function = $projectDB->createDocument([
'$collection' => Database::SYSTEM_COLLECTION_FUNCTIONS,
'$permissions' => [
@ -37,12 +37,14 @@ $utopia->post('/v1/functions')
],
'dateCreated' => time(),
'dateUpdated' => time(),
'status' => 'paused',
'name' => $name,
'tag' => '',
'vars' => '', //$vars, // TODO Should be encrypted
'trigger' => $trigger,
'vars' => $vars,
'events' => $events,
'schedule' => $schedule,
'previous' => null,
'next' => null,
'timeout' => $timeout,
]);
@ -116,25 +118,28 @@ $utopia->put('/v1/functions/:functionId')
->param('functionId', '', function () { return new UID(); }, 'Function unique ID.')
->param('name', '', function () { return new Text(128); }, 'Function name.')
->param('vars', [], function () { return new Assoc();}, 'Key-value JSON object.', true)
->param('trigger', 'event', function () { return new WhiteList(['event', 'scheudle']); }, 'Function trigger type.', true)
->param('events', [], function () { return new ArrayList(new Text(256)); }, 'Events list.', true)
->param('schedule', '', function () { return new Cron(); }, 'Schedule CRON syntax.', true)
->param('timeout', 15, function () { return new WhiteList([5, 15, 30, 60]); }, 'Function maximum execution time in seconds.', true)
->param('timeout', 15, function () { return new Range(0, 60); }, 'Function maximum execution time in seconds.', true)
->action(
function ($functionId, $name, $vars, $trigger, $events, $schedule, $timeout) use ($response, $projectDB) {
function ($functionId, $name, $vars, $events, $schedule, $timeout) use ($response, $projectDB) {
$function = $projectDB->getDocument($functionId);
if (empty($function->getId()) || Database::SYSTEM_COLLECTION_FUNCTIONS != $function->getCollection()) {
throw new Exception('Function not found', 404);
}
$cron = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? CronExpression::factory($schedule) : null;
$next = (!empty($function->getAttribute('tag', null)) && !empty($schedule)) ? $cron->getNextRunDate()->format('U') : null;
$function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [
'dateUpdated' => time(),
'name' => $name,
'vars' => '', //$vars, //TODO Should be encrypted
'trigger' => $trigger,
'vars' => $vars,
'events' => $events,
'schedule' => $schedule,
'previous' => null,
'next' => $next,
'timeout' => $timeout,
]));
@ -163,8 +168,13 @@ $utopia->patch('/v1/functions/:functionId/tag')
throw new Exception('Function not found', 404);
}
$schedule = $function->getAttribute('schedule', '');
$cron = (!empty($function->getAttribute('tag')&& !empty($schedule))) ? CronExpression::factory($schedule) : null;
$next = (!empty($function->getAttribute('tag')&& !empty($schedule))) ? $cron->getNextRunDate()->format('U') : null;
$function = $projectDB->updateDocument(array_merge($function->getArrayCopy(), [
'tag' => $tag,
'next' => $next,
]));
if (false === $function) {

View file

@ -36,13 +36,175 @@ $events = array_keys($this->getParam('events', []));
<div class="zone xl">
<ul class="phases clear" data-ui-phases data-selected="{{router.params.tab}}">
<li data-state="/console/functions/function?id={{router.params.id}}&project={{router.params.project}}">
<h2>Tags</h2>
<h2>Overview</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<div class="box margin-bottom-large">
<div class="text-align-center">
<img src="" data-ls-attrs="src=/images/environments/nodejs.png" alt="Function Env." class="avatar huge margin-top-negative-xxl" />
<p class="margin-bottom-tiny">
Default Tag: #5eba35ec489b9
</p>
<p class="text-fade margin-bottom">
Node.js 14.2
</p>
<button class="margin-bottom-small">Execute Now</button> &nbsp; <button class="reverse">View Logs</button>
</div>
</div>
<h3>Usage</h3>
<div class="box margin-bottom-small">
<div class="margin-start-negative-small margin-end-negative-small margin-top-negative-small margin-bottom-negative-small">
<div class="chart margin-bottom-no">
<div class="content" data-forms-chart="Requests=usage.requests.data,Network=usage.network.data"></div>
</div>
</div>
</div>
<ul class="chart-notes margin-bottom-large">
<li>Invocations</li>
<li>CPU Time</li>
</ul>
<h3>Tags</h3>
<div class="margin-bottom"
data-service="functions.listTags"
data-scope="sdk"
data-event="load,functions.createTag,functions.deleteTag"
data-name="project-functions"
data-param-project-id="{{router.params.project}}"
data-success="trigger"
data-success-param-trigger-events="functions.list">
<div data-ls-if="0 == {{project-functions.functions.length}} || undefined == {{project-functions.functions.length}}" class="box margin-top margin-bottom">
<h3 class="margin-bottom-small text-bold">No Tags Found</h3>
<p class="margin-bottom-no">You haven't uploaded any tags for your function yet.</p>
</div>
<div data-ls-if="0 != {{project-functions.functions.length}}">
<div class="box margin-bottom">
<ul data-ls-loop="project-functions.functions" data-ls-as="function" class="list">
<li class="clear">
<a data-ls-attrs="href=/console/functions/function?id={{function.$id}}&project={{router.params.project}}" class="button pull-end">Set Default</a>
<span data-ls-bind="{{function.name}}"></span>
</li>
</ul>
</div>
</div>
</div>
<div class="pull-start">
<div data-ui-modal class="modal close box sticky-footer" data-button-text="Add Tag">
<button type="button" class="close pull-end" data-ui-modal-close=""><i class="icon-cancel"></i></button>
<h1>Add Function</h1>
<form
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Create Project Function"
data-service="functions.create"
data-scope="sdk"
data-event="submit"
data-success="alert,trigger,reset"
data-success-param-alert-text="Created function successfully"
data-success-param-trigger-events="functions.create"
data-failure="alert"
data-failure-param-alert-text="Failed to create function"
data-failure-param-alert-classname="error">
<label for="name">Name</label>
<input type="text" id="name" name="name" required autocomplete="off" class="margin-bottom-xl" />
<footer>
<button type="submit">Create</button> &nbsp; <button data-ui-modal-close="" type="button" class="reverse">Cancel</button>
</footer>
</form>
</div>
</div>
<div class="pull-end paging">
<form
data-service="users.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-users"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-back data-offset="{{router.params.offset}}" data-sum="{{project-users.sum}}" class="margin-end round small" aria-label="Back"><i class="icon-left-open"></i></button>
</form>
<span data-ls-bind="{{router.params.offset|pageCurrent}} / {{project-users.sum|pageTotal}}"></span>
<form
data-service="users.list"
data-event="submit"
data-param-search="{{router.params.search}}"
data-param-limit="<?php echo APP_PAGING_LIMIT; ?>"
data-param-order-type="DESC"
data-scope="sdk"
data-name="project-users"
data-success="state"
data-success-param-state-keys="search,offset">
<button name="offset" data-paging-next data-offset="{{router.params.offset}}" data-sum="{{project-users.sum}}" class="margin-start round small" aria-label="Next"><i class="icon-right-open"></i></button>
</form>
</div>
</div>
<div class="col span-4 sticky-top margin-bottom">
<label>Function ID</label>
<div class="input-copy margin-bottom">
<input id="uid" type="text" autocomplete="off" placeholder="" data-ls-bind="{{project-function.$id}}" disabled data-forms-copy>
</div>
<ul class="margin-bottom-large text-fade text-size-small">
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> <button data-ls-ui-trigger="open-json" class="link text-size-small">View as JSON</button></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Last Updated: <span data-ls-bind="{{project-function.dateUpdated|date-text}}"></span></li>
<li class="margin-bottom-small"><i class="icon-angle-circled-right margin-start-tiny margin-end-tiny"></i> Created: <span data-ls-bind="{{project-function.dateCreated|date-text}}"></span></li>
</ul>
<form name="functions.delete" class="margin-bottom"
data-analytics-event="submit"
data-analytics-category="console"
data-analytics-label="Delete Function"
data-service="functions.delete"
data-event="submit"
data-param-function-id="{{router.params.id}}"
data-confirm="Are you sure you want to delete this function?"
data-success="alert,trigger,redirect"
data-success-param-alert-text="Function deleted successfully"
data-success-param-trigger-events="functions.delete"
data-success-param-redirect-url="/console/functions?project={{router.params.project}}"
data-failure="alert"
data-failure-param-alert-text="Failed to delete function"
data-failure-param-alert-classname="error">
<button type="submit" class="danger fill">Delete Function</button>
</form>
</div>
</div>
</li>
<li data-state="/console/functions/function/settings?id={{router.params.id}}&project={{router.params.project}}">
<h2>Settings</h2>
<div class="row responsive margin-top-negative">
<div class="col span-8">
<div class="col span-8 margin-bottom">
<label>&nbsp;</label>
<form
data-analytics-event="submit"
data-analytics-category="console"
@ -58,13 +220,11 @@ $events = array_keys($this->getParam('events', []));
data-failure-param-alert-text="Failed to update function"
data-failure-param-alert-classname="error">
<label>&nbsp;</label>
<div class="box">
<label for="name">Name</label>
<input name="name" id="function-name" type="text" autocomplete="off" data-ls-bind="{{project-function.name}}" data-forms-text-direction required placeholder="Function Name" />
<label for="timeout">Timeout</label>
<label for="timeout">Timeout <span class="tooltip small" data-tooltip="Limit the execution time of your function."><i class="icon-info-circled"></i></span></label>
<select name="timeout" data-ls-bind="{{project-function.timeout}}" required>
<option value="5">5 seconds</option>
<option value="15">15 seconds</option>
@ -74,16 +234,25 @@ $events = array_keys($this->getParam('events', []));
<!-- <option value="900" disabled>15 minutes</option> -->
</select>
<label for="function-trigger">Trigger</label>
<label for="events">Events <span class="tooltip small" data-tooltip="Choose which events should trigger this function."><i class="icon-info-circled"></i></span></label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large">
<input type="checkbox" name="events" data-ls-bind="{{project-function.events}}" value="<?php echo $event; ?>" /> &nbsp; <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<select name="trigger" data-ls-bind="{{project-function.trigger}}" required>
<option value="event">On Event</option>
<option value="schedule">On Schedule</option>
</select>
<?php endforeach; ?>
</div>
<div data-ls-template="template-trigger-{{project-function.trigger}}" data-type="script" class="margin-bottom"></div>
<label for="schedule">Schedule (CRON Syntax) <span class="tooltip small" data-tooltip="Set a CRON schedule to trigger this function."><i class="icon-info-circled"></i></span></label>
<input type="text" class="full-width" name="schedule" autocomplete="off" data-ls-bind="{{project-function.schedule}}" placeholder="* * * * *" />
<div class="text-size-small text-fade margin-bottom margin-top-negative-small">Leave blank for no schedule</div>
<h3 class="margin-bottom-small">Variables</h3>
<h3 class="margin-bottom-small">Variables <span class="tooltip small" data-tooltip="Set variables or secret keys that will be passed as env vars to your function."><i class="icon-info-circled"></i></span></h3>
<div data-ls-if="(!{{project-function.vars.length}})">
<hr class="margin-bottom margin-top-no" />
@ -93,7 +262,7 @@ $events = array_keys($this->getParam('events', []));
<div class="margin-bottom-small">
<div data-forms-remove class="row thin">
<div class="col span-10">
<input type="hidden" data-forms-key-value data-ls-attrs="name={{$index}}" data-ls-bind="{{var}}" data-cast-to="array" />
<input type="hidden" data-forms-key-value data-ls-attrs="name={{$index}}" data-ls-bind="{{var}}" />
</div>
<div class="col span-2">
<button type="button" data-remove class="reverse danger round pull-end"><i class="icon-cancel"></i></button>
@ -157,26 +326,4 @@ $events = array_keys($this->getParam('events', []));
</li>
</ul>
</div>
</div>
<script type="text/html" id="template-trigger-event">
<label for="events">Events</label>
<div class="row responsive thin">
<?php foreach ($events as $i => $event) : ?>
<div class="col span-6 text-one-liner margin-bottom text-height-large">
<input type="checkbox" name="events" data-ls-bind="{{project-function.events}}" value="<?php echo $event; ?>" /> <?php echo $event; ?>
</div>
<?php if (($i + 1) % 2 === 0) : ?>
</div>
<div class="row responsive thin">
<?php endif; ?>
<?php endforeach; ?>
</div>
</script>
<script type="text/html" id="template-trigger-schedule">
<label for="schedule">Schedule (CRON Syntax)</label>
<input type="text" class="full-width" name="schedule" required autocomplete="off" data-ls-bind="{{project-function.schedule}}" placeholder="* * * * *" />
</script>
</div>

View file

@ -33,15 +33,7 @@
<a data-ls-attrs="href=/console/functions/function?id={{function.$id}}&project={{router.params.project}}" class="button pull-end">Settings</a>
<span data-ls-bind="{{function.name}}"></span> &nbsp; (<span data-ls-bind="{{function.events.length}}"></span> events)
<span data-ls-if="0 == {{function.security}}">
&nbsp; <small class="text-danger">(SSL/TLS Disabled)</small>
</span>
<div class="margin-top-tiny">
<a data-ls-attrs="href={{function.url}}" data-ls-bind="{{function.url}}" target="_blank" class="text-one-liner"></a>
</div>
<span data-ls-bind="{{function.name}}"></span> &nbsp; <span data-ls-if="({{function.events.length}})">(<span data-ls-bind="{{function.events.length}}"></span> events)</span>
</li>
</ul>
</div>
@ -79,7 +71,7 @@
</li>
<!-- <li data-state="/console/users/teams?project={{router.params.project}}">
<h2>Teams</h2>
<h2>Usage</h2>
</li> -->
</ul>
</div>

View file

@ -42,10 +42,27 @@ class FunctionsV1
{
global $environments;
/*
* 1. Get Original Task
* 2. Check for updates
* If has updates skip task and don't reschedule
* If status not equal to play skip task
* 3. Check next run date, update task and add new job at the given date
* 4. Execute task (set optional timeout)
* 5. Update task response to log
* On success reset error count
* On failure add error count
* If error count bigger than allowed change status to pause
*/
/**
* 1. Get event args
* 2. Unpackage code in an isolated folder
* 3. Execute in container with timeout + messure execution time
* 3. Execute in container with timeout
* + messure execution time
* + pass env vars
* + pass one-time api key
* 4. Update execution status
* 5. Update execution stdout & stderr
* 6. Trigger audit log
@ -58,6 +75,8 @@ class FunctionsV1
$start = microtime(true);
//TODO aviod scheduled execution if delay is bigger than X offest
/**
* Limit CPU Usage
* Limit Memory Usage

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB