diff --git a/README.md b/README.md index cd8ba693..30ad78b1 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ The payments API keeps a reference to the running workflow via its task ID. Oper ### Running with docker-compose -Download our latest install script, setup your environment and launch your own local instance of µTask. +Download our latest install script, setup your environment and launch your own local instance of µTask. ```bash mkdir utask && cd utask @@ -106,7 +106,7 @@ You'll deploy your version of µTask by building a docker image based on the off ### Architecture -µTask is designed to run a task scheduler and perform the task workloads within a single runtime: work is not delegated to external agents. Multiple instances of the application will coordinate around a single postgres database: each will be able to determine independently which tasks are available. When an instance of µTask decides to execute a task, it will take hold of that task to avoid collisions, then release it at the end of an execution cycle. +µTask is designed to run a task scheduler and perform the task workloads within a single runtime: work is not delegated to external agents. Multiple instances of the application will coordinate around a single postgres database: each will be able to determine independently which tasks are available. When an instance of µTask decides to execute a task, it will take hold of that task to avoid collisions, then release it at the end of an execution cycle. A task will keep running as long as its steps are successfully executed. If a task's execution is interrupted before completion, it will become available to be re-collected by one of the active instances of µTask. That means that execution might start in one instance and resume on a different one. @@ -125,7 +125,7 @@ A task will keep running as long as its steps are successfully executed. If a ta ## Configuration 🔨 -### Command line args +### Command line args The µTask binary accepts the following arguments as binary args or env var. All are optional and have a default value: @@ -190,7 +190,7 @@ A user can be allowed to resolve a task in three ways: - `auto_runnable`; boolean (default: false): when true, the task will be executed directly after being created, IF the requester is an accepted resolver or `allow_all_resolver_usernames` is true - `blocked`: boolean (default: false): no tasks can be created from this template - `hidden`: boolean (default: false): the template is not listed on the API, it is concealed to regular users -- `retry_max`: int (default: 100): maximum amount of consecutive executions of a task based on this template, before being blocked for manual review +- `retry_max`: int (default: 100): maximum amount of consecutive executions of a task based on this template, before being blocked for manual review ### Inputs @@ -213,7 +213,7 @@ An input's definition allows to define validation constraints on the values prov A template variable is a named holder of either: - a fixed value -- a JS expression evaluated on the fly. +- a JS expression evaluated on the fly. See the example template above to see variables in action. The expression in a variable can contain template handles to introduce values dynamically (from executed steps, for instance), like a step's configuration. @@ -231,8 +231,8 @@ The flow of this sequence can further be controlled with **conditions** on the s - to analyze its outcome and override the engine's default behaviour Several conditions can be specified, the first one to evaluate as `true` is applied. A condition is composed of: -- a `type` (skip or check) -- a list of `if` assertions (`value`, `operator`, `expected`) which all have to be true (AND on the collection), +- a `type` (skip or check) +- a list of `if` assertions (`value`, `operator`, `expected`) which all have to be true (AND on the collection), - a `then` object to impact the state of steps (`this` refers to the current step) - an optional `message` to convey the intention of the condition, making it easier to inspect tasks @@ -265,7 +265,7 @@ steps: getUser: description: Get user custom_states: [NOT_FOUND] - action: + action: type: http configuration: url: http://example.org/user/{{.input.id}} @@ -281,12 +281,12 @@ steps: message: User {{.input.id}} not found ``` -#### Basic Step Properties +#### Basic Step Properties - `name`: a unique identifier - `description`: a human readable sentence to convey the step's intent - `dependencies`: a list of step names on which this step waits before running -- `retry_pattern`: (seconds|minutes|hours) define on what temporal order of magnitude the re-runs of this step should be spread +- `retry_pattern`: (seconds|minutes|hours) define on what temporal order of magnitude the re-runs of this step should be spread #### Action @@ -299,7 +299,7 @@ base_configurations: postMessage: method: POST url: http://message.board/new -``` +``` This base configuration can then be leveraged by any step wanting to post a message, with different bodies: @@ -322,7 +322,7 @@ steps: body: Goodbye ``` -These two step definitions are the equivalent of: +These two step definitions are the equivalent of: ```yaml steps: @@ -381,7 +381,7 @@ Will render the following output, a combination of the action's raw output and t Browse [builtin actions](./pkg/plugins/builtin) -|Plugin name|Description|Documentation +|Plugin name|Description|Documentation |---|---|--- |**`echo`** | Print out a pre-determined result | [Access plugin doc](./pkg/plugins/builtin/echo/README.md) |**`http`** | Make an http request | [Access plugin doc](./pkg/plugins/builtin/http/README.md) @@ -392,7 +392,7 @@ Browse [builtin actions](./pkg/plugins/builtin) |**`email`** | Send an email | [Access plugin doc](./pkg/plugins/builtin/email/README.md) |**`ping`** | Send a ping to an hostname *Warn: This plugin will keep running until the count is done* | [Access plugin doc](./pkg/plugins/builtin/ping/README.md) |**`script`** | Execute a script under `scripts` folder | [Access plugin doc](./pkg/plugins/builtin/script/README.md) - + #### Loops A step can be configured to take a json-formatted collection as input, in its `foreach` property. It will be executed once for each element in the collection, and its result will be a collection of each iteration. This scheme makes it possible to chain several steps with the `foreach` property. @@ -402,7 +402,7 @@ For the following step definition (note json-format of `foreach`): steps: prefixStrings: description: Process a collection of strings, adding a prefix - foreach: '[{"id":"a"},{"id":"b"},{"id":"c"}]' + foreach: '[{"id":"a"},{"id":"b"},{"id":"c"}]' action: type: echo configuration: @@ -426,6 +426,42 @@ This output can be then passed to another step in json format: foreach: '{{.step.prefixStrings.children | jsonmarshal}}' ``` +### Task templates validation + +A JSON-schema file is available to validate the syntax of task templates, it's available in `hack/template-schema.json`. + +Validation can be performed at writing time if you are using a modern IDE or editor. + +#### Validation with Visual Studio Code + +- Install YAML extension from RedHat. + - Ctrl+P, then type `ext install redhat.vscode-yaml` +- Edit your workspace configuration (`settings.json` file) to add: +```json +{ + "yaml.schemas": { + "./hack/template-schema.json": ["/*.yaml"] + } +} +``` +- Every templates will get real-time validation while writing. + +![](./assets/img/vscode_template_validation.png) + +#### Task templates snippets with Visual Studio Code + +Code snippets are available in this repository to be used for task template writing: `hack/templates.code-snippets` + +To use them inside your repository, copy the `templates.code-snippets` file into your `.vscode` workspace folder. + +Available snippets: +- template +- variable +- input +- step + +![](./assets/img/vscode_code_snippets_templates.gif) + ## Extending µTask with plugins µTask is extensible with [golang plugins](https://golang.org/pkg/plugin/) compiled in *.so format. Two kinds of plugins exist: @@ -438,7 +474,7 @@ The installation script for utask creates a folder structure that will automatic Action plugins allow you to extend the kind of work that can be performed during a task. An action plugin has a name, that will be referred to as the action `type` in a template. It declares a configuration structure, a validation function for the data received from the template as configuration, and an execution function which performs an action based on valid configuration. -Create a new folder within the `plugins` folder of your utask repo. There, develop a `main` package that exposes a `Plugin` variable that implements the `TaskPlugin` defined in the `plugins` package: +Create a new folder within the `plugins` folder of your utask repo. There, develop a `main` package that exposes a `Plugin` variable that implements the `TaskPlugin` defined in the `plugins` package: ```golang type TaskPlugin interface { @@ -451,7 +487,7 @@ type TaskPlugin interface { } ``` -The `taskplugin` [package](./pkg/plugins/taskplugin/taskplugin.go) provides helper functions to build a Plugin: +The `taskplugin` [package](./pkg/plugins/taskplugin/taskplugin.go) provides helper functions to build a Plugin: ```golang package main @@ -482,9 +518,9 @@ func exec(stepName string, config interface{}, ctx interface{}) (output interfac ### Init Plugins -Init plugins allow you to customize your instance of µtask by giving you access to its underlying configuration store and its API server. +Init plugins allow you to customize your instance of µtask by giving you access to its underlying configuration store and its API server. -Create a new folder within the `init` folder of your utask repo. There, develop a `main` package that exposes a `Plugin` variable that implements the `InitializerPlugin` defined in the `plugins` package: +Create a new folder within the `init` folder of your utask repo. There, develop a `main` package that exposes a `Plugin` variable that implements the `InitializerPlugin` defined in the `plugins` package: ```golang type Service struct { @@ -505,7 +541,7 @@ As of version `v1.0.0`, this is meant to give you access to two features: If you develop more than one initialization plugin, they will all be loaded in alphabetical order. You might want to provide a default initialization, plus more specific behaviour under certain scenarios. ## Contributing - + ### Backend In order to iterate on feature development, run the utask server plus a backing postgres DB by invoking `make run-test-stack-docker` in a terminal. Use SIGINT (`Ctrl+C`) to reboot the server, and SIGQUIT (`Ctrl+4`) to teardown the server and its DB. @@ -514,7 +550,7 @@ In a separate terminal, rebuild (`make re`) each time you want to iterate on a c ### Frontend -µTask serves two graphical interfaces: one for general use of the tool (`dashboard`), the other one for task template authoring (`editor`). They're found in the `ui` folder and each have their own Makefile for development purposes. +µTask serves two graphical interfaces: one for general use of the tool (`dashboard`), the other one for task template authoring (`editor`). They're found in the `ui` folder and each have their own Makefile for development purposes. Run `make dev` to launch a live-reloading on your machine. The editor is a standalone GUI, while the dashboard needs a backing µTask api (see above to run a server). @@ -525,15 +561,15 @@ Run all test suites against an ephemeral postgres DB: ```bash $ make test-docker ``` - + ### Get in touch You've developed a new cool feature ? Fixed an annoying bug ? We'll be happy to hear from you! Take a look at [CONTRIBUTING.md](https://github.com/ovh/utask/blob/master/CONTRIBUTING.md) - + ## Related links - + * Contribute: [CONTRIBUTING.md](CONTRIBUTING.md) * Report bugs: [https://github.com/ovh/utask/issues](https://github.com/ovh/utask/issues) * Get latest version: [https://github.com/ovh/utask/releases](https://github.com/ovh/utask/releases) diff --git a/assets/img/vscode_code_snippets_templates.gif b/assets/img/vscode_code_snippets_templates.gif new file mode 100644 index 00000000..e7006b04 Binary files /dev/null and b/assets/img/vscode_code_snippets_templates.gif differ diff --git a/assets/img/vscode_template_validation.png b/assets/img/vscode_template_validation.png new file mode 100644 index 00000000..5e0b9747 Binary files /dev/null and b/assets/img/vscode_template_validation.png differ diff --git a/hack/template-schema.json b/hack/template-schema.json new file mode 100644 index 00000000..51a7744e --- /dev/null +++ b/hack/template-schema.json @@ -0,0 +1,905 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "title": "µTask template schema", + "description": "This validates a µTask template", + "additionalProperties": false, + "definitions": { + "Step": { + "type": "object", + "title": "A µTask template step", + "default": {}, + "additionalProperties": false, + "examples": [ + { + "description": "Get UTC time", + "action": { + "type": "http", + "configuration": { + "method": "GET", + "url": "http://worldclockapi.com/api/json/utc/now" + } + } + } + ], + "required": [ + "description", + "action" + ], + "properties": { + "description": { + "type": "string", + "title": "Step description", + "description": "Will be displayed to customers in UI.", + "default": "", + "examples": [ + "Get UTC time" + ] + }, + "retry_pattern": { + "type": "string", + "enum": [ + "seconds", + "minutes", + "hours" + ], + "title": "Retry pattern of the step", + "description": "Define on what temporal order of magnitude the re-runs of this step should be spread." + }, + "dependencies": { + "type": "array", + "description": "List of step names on which this step waits before running", + "items": { + "type": "string" + } + }, + "action": { + "$ref": "#/definitions/Action" + }, + "conditions": { + "type": "array", + "description": "Conditions before/after step executed", + "items": { + "$ref": "#/definitions/Condition" + } + }, + "custom_states": { + "type": "array", + "description": "Declares some custom state to be used within the step", + "items": { + "type": "string" + } + }, + "foreach": { + "type": "string", + "description": "Elements on which the step will loop" + } + } + }, + "Action": { + "oneOf": [ + { + "$ref": "#/definitions/ActionHTTP" + }, + { + "$ref": "#/definitions/ActionEcho" + }, + { + "$ref": "#/definitions/ActionScript" + }, + { + "$ref": "#/definitions/ActionSSH" + }, + { + "$ref": "#/definitions/ActionSubtask" + }, + { + "$ref": "#/definitions/ActionPing" + }, + { + "$ref": "#/definitions/ActionNotify" + }, + { + "$ref": "#/definitions/ActionEmail" + }, + { + "$ref": "#/definitions/ActionAPIOVH" + }, + { + "type": "object", + "title": "Generic action", + "additionalProperties": false, + "properties": { + "type": { + "not": { + "type": "string", + "enum": [ + "http", + "echo", + "script", + "ssh", + "subtask", + "ping", + "notify", + "email", + "apiovh" + ] + } + }, + "configuration": { + "type": "object", + "description": "Action configuration" + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + } + } + ] + }, + "ActionHTTP": { + "type": "object", + "properties": { + "type": { + "const": "http" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "required": [ + "url", + "method" + ], + "properties": { + "url": { + "type": "string" + }, + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "DELETE" + ] + }, + "body": { + "type": "string" + }, + "headers": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "timeout_seconds": { + "type": "string", + "pattern": "^\\d+$" + }, + "auth": { + "type": "object", + "properties": { + "basic": { + "type": "object", + "properties": { + "user": { + "type": "string" + }, + "password": { + "type": "string" + } + } + }, + "bearer": { + "type": "string" + } + } + }, + "deny_redirects": { + "type": "string", + "pattern": "^(true|false)$" + }, + "parameters": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + } + } + }, + "trim_prefix": { + "type": "string" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "HTTP Action", + "description": "HTTP action performs a HTTP call to a remote server", + "additionalProperties": false + }, + "ActionEcho": { + "type": "object", + "properties": { + "type": { + "const": "echo" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "error_type": { + "type": "string", + "enum": [ + "server", + "client" + ] + }, + "error_message": { + "type": "string" + }, + "output": { + "type": "object" + }, + "metadata": { + "type": "object" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "Echo Action", + "additionalProperties": false, + "description": "Echo action just prints some result as output" + }, + "ActionScript": { + "type": "object", + "properties": { + "type": { + "const": "script" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "file_path": { + "type": "string" + }, + "argv": { + "type": "array" + }, + "stdin": { + "type": "string" + }, + "timeout": { + "type": "string" + }, + "last_line_not_json": { + "type": "boolean" + }, + "allow_exit_non_zero": { + "type": "boolean" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "Script Action", + "additionalProperties": false, + "description": "Script action executes a script locally in the µTask instance" + }, + "ActionSSH": { + "type": "object", + "properties": { + "type": { + "const": "ssh" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "user": { + "type": "string" + }, + "hops": { + "type": "array", + "items": { + "type": "string" + } + }, + "target": { + "type": "string" + }, + "script": { + "type": "string" + }, + "result": { + "type": "object" + }, + "ssh_key": { + "type": "string" + }, + "ssh_key_passphrase": { + "type": "string" + }, + "allow_exit_non_zero": { + "type": "boolean" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "SSH Action", + "additionalProperties": false, + "description": "SSH action will performs a SSH connection to a remote host, and executes a command" + }, + "ActionSubtask": { + "type": "object", + "properties": { + "type": { + "const": "subtask" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "template": { + "type": "string" + }, + "input": { + "type": "object" + }, + "resolver_usernames": { + "type": "string" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "Subtask Action", + "additionalProperties": false, + "description": "Subtask action will spawn a subtask of the specified template, and wait for resolution" + }, + "ActionPing": { + "type": "object", + "properties": { + "type": { + "const": "ping" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "hostname": { + "type": "string" + }, + "count": { + "type": "string", + "pattern": "^\\d+$" + }, + "interval_second": { + "type": "string", + "pattern": "^\\d+$" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "Ping Action", + "additionalProperties": false, + "description": "Ping action will perform a ping to a remote server to verify connectivity" + }, + "ActionNotify": { + "type": "object", + "properties": { + "type": { + "const": "notify" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "properties": { + "message": { + "type": "string" + }, + "backends": { + "type": "array", + "items": { + "type": "string" + } + }, + "fields": { + "type": "object" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "Notify Action", + "additionalProperties": false, + "description": "Notify action will send a notification when executed" + }, + "ActionEmail": { + "type": "object", + "properties": { + "type": { + "const": "email" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "required": [ + "smtp_username", + "smtp_password", + "smtp_port", + "smtp_hostname", + "from_address", + "to", + "subject", + "body" + ], + "properties": { + "smtp_username": { + "type": "string" + }, + "smtp_password": { + "type": "string" + }, + "smtp_port": { + "type": "string", + "pattern": "^\\d+$" + }, + "smtp_skip_tls_verify": { + "type": "string", + "pattern": "^(true|false)$" + }, + "from_address": { + "type": "string" + }, + "from_name": { + "type": "string" + }, + "subject": { + "type": "string" + }, + "to": { + "type": "array", + "items": { + "type": "string" + } + }, + "body": { + "type": "object" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "Email Action", + "additionalProperties": false, + "description": "Email action will send an email when executed" + }, + "ActionAPIOVH": { + "type": "object", + "properties": { + "type": { + "const": "apiovh" + }, + "configuration": { + "type": "object", + "additionalProperties": false, + "required": [ + "method", + "path" + ], + "properties": { + "method": { + "type": "string", + "enum": [ + "GET", + "POST", + "PUT", + "DELETE" + ] + }, + "path": { + "type": "string" + }, + "credentials": { + "type": "string" + }, + "body": { + "type": "object" + } + } + }, + "base_configuration": { + "type": "object" + }, + "base_output": { + "type": "object" + } + }, + "title": "APIOVH Action", + "additionalProperties": false, + "description": "APIOVH action will perform an API call to OVH systems" + }, + "Condition": { + "type": "object", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "description": "Indicates if condition applies before (skip) or after (check) step is running", + "enum": [ + "skip", + "check" + ] + }, + "if": { + "type": "array", + "items": { + "type": "object", + "properties": { + "value": { + "type": "string", + "description": "Value on which the condition applies" + }, + "operator": { + "type": "string", + "description": "Operator used by the condition", + "enum": [ + "EQ", + "NE", + "GT", + "LT", + "GE", + "LE", + "REGEXP" + ] + }, + "expected": { + "type": "string", + "description": "Expected value for the condition to be true" + }, + "message": { + "type": "string" + } + } + } + }, + "then": { + "type": "object", + "description": "Describes state changement if all conditions match" + }, + "message": { + "type": "string", + "description": "Message to display in the UI if condition match" + } + }, + "description": "Conditions to apply on the step" + }, + "Input": { + "type": "object", + "additionalProperties": false, + "examples": [ + { + "name": "language", + "default": "english", + "optional": true, + "description": "The language in which you wish to greet the world", + "legal_values": [ + "english", + "spanish" + ] + } + ], + "required": [ + "name", + "description", + "legal_values", + "optional", + "default" + ], + "properties": { + "name": { + "type": "string", + "description": "Input name - to be used inside template as .input.name", + "default": "", + "examples": [ + "language" + ] + }, + "description": { + "type": "string", + "description": "Description displayed in the UI", + "default": "", + "examples": [ + "The language in which you wish to greet the world" + ] + }, + "regex": { + "type": "string", + "description": "Regex to validate the input value", + "default": "", + "examples": [ + "^[a-z\\d]+$" + ] + }, + "type": { + "type": "string", + "description": "Input value type", + "enum": [ + "string", + "number", + "bool" + ], + "examples": [ + "bool" + ] + }, + "legal_values": { + "type": "array", + "description": "Restrict input value to some values", + "default": [], + "items": { + "type": "string" + } + }, + "optional": { + "type": "boolean", + "description": "Indicates if input is optional or not", + "default": false + }, + "default": { + "type": "string", + "description": "Default value of the input if not fulfilled", + "examples": [ + "english" + ] + }, + "collection": { + "type": "boolean", + "description": "Indicates if multiple values can be specified for this input", + "default": false + } + } + }, + "Variable": { + "type": "object", + "additionalProperties": false, + "examples": [ + { + "name": "english-message", + "value": "Hello World!" + }, + { + "expression": "// a short javascript snippet\nvar h = 'Hola';\nvar m = 'mundo';\nh + ' ' + m + '!';", + "name": "spanish-message" + } + ], + "required": [ + "name" + ], + "properties": { + "name": { + "type": "string", + "description": "Variable name", + "default": "", + "examples": [ + "english-message" + ] + }, + "value": { + "type": "string", + "description": "Fixed value for the variable (cannot be templated)", + "default": "", + "examples": [ + "Hello World!" + ] + }, + "expression": { + "type": "string", + "description": "Javascript expression that will be evaluated (can be templated)" + } + } + } + }, + "required": [ + "name", + "description", + "title_format", + "steps" + ], + "properties": { + "name": { + "type": "string", + "description": "Template name", + "default": "", + "examples": [ + "hello-world-now" + ] + }, + "description": { + "type": "string", + "description": "Template short description", + "default": "", + "examples": [ + "Say hello to the world, now!" + ] + }, + "long_description": { + "type": "string", + "description": "Template long description (will be displayed to customer when selecting templates)", + "default": "", + "examples": [ + "This task prints out a greeting to the entire world, after retrieving the current UTC time from an external API" + ] + }, + "doc_link": { + "type": "string", + "description": "Template documentation URL", + "default": "", + "examples": [ + "https://en.wikipedia.org/wiki/%22Hello,_World!%22_program" + ] + }, + "title_format": { + "type": "string", + "description": "Template title (will be used when task are created using this template)", + "default": "", + "examples": [ + "Say hello in {{.input.language}}" + ] + }, + "result_format": { + "type": "object", + "description": "Describes the object format that will be saved as task result", + "default": {}, + "examples": [ + { + "echo_message": "{{.step.sayHello.output.message}}", + "echo_when": "{{.step.sayHello.output.when}}" + } + ] + }, + "allowed_resolver_usernames": { + "type": "array", + "description": "Usernames of people allowed to resolve a task based on this template", + "default": [], + "items": { + "type": "string" + } + }, + "allow_all_resolver_usernames": { + "type": "boolean", + "description": "Indicates if any user can resolve a task based on this template", + "default": false, + "examples": [ + true + ] + }, + "auto_runnable": { + "type": "boolean", + "description": "Indicates if task will run automatically with validation if requester is a valid resolver", + "default": false, + "examples": [ + true + ] + }, + "blocked": { + "type": "boolean", + "description": "Indicates if tasks can be created from this template", + "default": false, + "examples": [ + false + ] + }, + "hidden": { + "type": "boolean", + "description": "Indicates if template will be listed for regular users", + "default": false, + "examples": [ + false + ] + }, + "retry_max": { + "type": "integer", + "minimum": 0, + "description": "Maximum amount of consecutive executions of a task based on this template", + "default": 100, + "examples": [ + 100 + ] + }, + "variables": { + "type": "array", + "description": "Variables that can be used inside this templates", + "default": [], + "items": { + "$ref": "#/definitions/Variable" + } + }, + "inputs": { + "type": "array", + "description": "Inputs that should be provided when creating a task based on this template", + "default": [], + "items": { + "$ref": "#/definitions/Input" + } + }, + "steps": { + "type": "object", + "description": "Steps that will be executed when a task based on this template is created", + "default": {}, + "patternProperties": { + ".*": { + "$ref": "#/definitions/Step" + } + } + }, + "base_configurations": { + "type": "object", + "description": "Global configurations that will be used as base for steps inside this template" + } + } +} \ No newline at end of file diff --git a/hack/templates.code-snippets b/hack/templates.code-snippets new file mode 100644 index 00000000..0636af4f --- /dev/null +++ b/hack/templates.code-snippets @@ -0,0 +1,81 @@ +{ + "utask-template": { + "scope": "yaml", + "prefix": "template", + "body": [ + "name: ${1:my-new-template}", + "description: ${2:New template}", + "long_description: ${2:New template to do stuff}", + "doc_link: ", + "", + "title_format: ${3:New task for my-new-template}", + "result_format:", + " ${4:foo: bar}", + "", + "allowed_resolver_usernames: [${5:admin}]", + "allow_all_resolver_usernames: ${6|true,false|}", + "auto_runnable: ${7|true,false|}", + "blocked: ${8|true,false|}", + "hidden: ${9|true,false|}", + "", + "", + "inputs:", + "", + "", + "variables:", + "", + "", + "steps:" + ], + "description": "Generate a basic template skeleton" + }, + "utask-input": { + "scope": "yaml", + "prefix": "input", + "body": [ + "- name: ${1:param1}", + " description: ${2:Choose your first param}", + " type: ${3|string,number,bool|}", + " legal_values: [${4}]", + " regex: \"${5:^\\d+$}\"", + " optional: ${6|false,true|}", + " default: \"${7}\"", + " collection: ${8|false,true|}" + ], + "description": "Generate an task template input" + }, + "utask-variable": { + "scope": "yaml", + "prefix": "variable", + "body": [ + "- name: ${1:var1}", + " value: ${2:foobar}", + " expression: >-", + " ${3:var s = 'stuff';}" + ], + "description": "Generate a variable" + }, + "utask-step": { + "scope": "yaml", + "prefix": "step", + "body": [ + " ${1:createStuff}:", + " description: ${2:Create stuff}", + " dependencies: [${3}]", + " action:", + " type: ${4|echo,notify,script,ssh,http,subtask,apiovh,email,ping|}", + " configuration:", + " ${5:foobar: buzz}", + " conditions:", + " - type: ${6|check,skip|}", + " if:", + " - value: '${7}'", + " operator: ${8|EQ,NE,GT,LT,GE,LE,REGEXP|}", + " expected: '${9}'", + " then:", + " ${10:this: NOT_FOUND}", + " message: ${11:User not found}" + ], + "description": "Generate a basic step for task template" + } +} \ No newline at end of file