Extending tasks ¶
Introduction ¶
RKD is designed to provide ready-to-configure automations. In practice you can install almost ready set of tasks using Python’s PIP tool, then adjust those tasks to your needs.
Every Base Task implements some mechanism, in this chapter we will use Docker Container as an example.
Given you have a Base Task RunInContainerBaseTask, it lets you do something, while a container is running.
You can execute commands in container, copy files between host and the container.
According to the RunInContainerBaseTask's documentation you need to extend the it.
Which means to make your own task that extends RunInContainerBaseTask as a base,
then override a method, put your code.
Voilà! Your own task basing on RunInContainerBaseTask is now ready to be executed!
Practical tips ¶
In order to successfully extend a Base Task a few steps needs to be marked. Check Base Task documentation, especially for:
The list of methods that are recommended to be extended
Which methods could be used in
configure()
and which inexecute()
Does the Base Task implement
inner_execute()
Note which methods to override needs to keep parent call, and if the parent should be called before or after the child (method that overrides parent)
Caution
inner_execute() will not work if it was not implemented by parent task. The sense of existence of inner_execute() is that it should be executed inside execute() at best moment of Base Task.
Hint
To avoid compatibility issues when upgrading Base Task version use only documented methods
Decorators ¶
There are three decorators that allows to decide if the parent method will be executed:
-
@after_parent (from rkd.core.api.decorators): Execute our method after original method
-
@before_parent (from rkd.core.api.decorators): Execute our method before original method
-
@without_parent (from rkd.core.api.decorators): Do not execute parent method at all
Important
No decorator in most case means that the parent method will not be executed at all
Caution
Not all methods supports decorators. For example argument parsing always inherits argument parsing from parent. Decorators can be used for configure, compile, execute, inner_execute.
Warning
Using multiple decorators for single method is not allowed and leads to syntax validation error.
execute@after_parent: |
print('I will e executed after parent method will be')
@before_parent
def execute(task: PythonSyntaxTask, ctx: ExecutionContext):
print('I will e executed before parent method will be')
def execute(self, ctx: ExecutionContext):
print('BEFORE PARENT')
super().execute(ctx) # make a parent method call
print('AFTER PARENT')
Example #1: Using inner_execute ¶
Check RunInContainerBaseTask documentation first. It says that execute() should not be overridden, but inner_execute() should be used instead.
Allows to work inside of a temporary docker container.
Configuration:
mount(): Mount directories/files as volumes
add_file_to_copy(): Copy given files to container before container starts
user: Container username, defaults to “root”
shell: Shell binary path, defaults to “/bin/sh”
docker_image: Full docker image name with registry (optional), group, image name and tag
entrypoint: Entrypoint
command: Command to execute on entrypoint
Runtime:
copy_to_container(): Copy files/directory to container immediately
in_container(): Execute inside container
Example:
version: org.riotkit.rkd/yaml/v1 imports: - rkd.core.standardlib.docker.RunInContainerBaseTask tasks: :something-in-docker: extends: rkd.core.standardlib.docker.RunInContainerBaseTask configure: | self.docker_image = 'php:7.3' self.user = 'www-data' self.mount(local='./build', remote='/build') self.add_file_to_copy('build.php', '/build/build.php') inner_execute: | self.in_container('php build.php') return True # do not extend just "execute", because "execute" is used by RunInContainerBaseTask # to spawn docker container, run inner_execute(), and after just destroy the container
Example #2: Advanced - extending a task that extends other task ¶
In Example #1 there is a base task that runs something inside a docker container , going further in Example #2 there is a task that runs any code in a PHP container.
Architecture:
-
Our example creates a Task from PhpScriptTask (we extend it, and create a “runnable” Task from it)
-
rkd.php.script.PhpScriptTask
extendsrkd.core.standardlib.docker.RunInContainerBaseTask
Again, to properly prepare your task basing on existing Base Task check the Base Task documentation for tips.
In case of
PhpScriptTask
the documentation says the parent
inner_execute
method should be executed to still allow providing PHP code via stdin.
To coexist parent and new method in place of
inner_execute
just use one of decorators to control the inheritance behavior.
Complete example:
Execute a PHP code (using a docker container) Can be extended - this is a base task.
Inherits settings from RunInContainerBaseTask .
Configuration:
script: Path to script to load instead of stdin (could be a relative path)
version: PHP version. Leave None to use default 8.0-alpine version
Example of usage:
version: org.riotkit.rkd/yaml/v2 imports: - rkd.php.script.PhpScriptTask tasks: :yaml:test:php: extends: rkd.php.script.PhpScriptTask configure@before_parent: | self.version = '7.2-alpine' inner_execute@after_parent: | self.in_container('php --version') print('IM AFTER PARENT. At first the PHP code from "input" will be executed.') return True input: | var_dump(getcwd()); var_dump(phpversion());Example of usage with MultiStepLanguageAgnosticTask:
version: org.riotkit.rkd/yaml/v1 tasks: :exec: environment: PHP: '7.4' IMAGE: 'php' steps: | #!rkd.php.script.PhpLanguage phpinfo();
Syntax reference ¶
Simplified Python |
Python Class |
YAML |
Description |
---|---|---|---|
get_steps(task: MultiStepLanguageAgnosticTask) -> List[str]: |
get_steps |
steps: [“”] |
List of steps in any language (only if extending MultiStep LanguageAgnosticTask) |
stdin() |
N/A |
input: “” |
Standard input text |
@extends(ClassName) decorator on a main method |
ClassName(BaseClass) |
extends: package.name.ClassName |
Which Base Task should be extended |
execute(task: BaseClassNameTask, ctx: ExecutionContext): |
execute(self, ctx: ExecutionContext) |
execute: “” |
Python code to execute |
inner_execute(task: BaseClassNameTask, ctx: ExecutionContext): |
inner_execute(self, ctx: ExecutionContext) |
inner_execute: “” |
Python code to execute inside inner_execute (if implemented by Base Task) |
compile(task: BaseClassNameTask, event: CompilationLifecycleEvent): |
compile(self, event: CompilationLifecycleEvent): |
N/A |
Python code to execute during Context compilation process |
configure(task: BaseClassNameTask, event: ConfigurationLifecycleEvent): |
configure(self, event: ConfigurationLifecycleEvent): |
configure: “” |
Python code to execute during Task configuration process |
get_description() |
get_description(self) |
description: “” |
Task description |
get_group_name() |
get_group_name() |
N/A |
Group name |
internal=True in TaskDeclaration |
internal=True in TaskDeclaration |
internal: False |
Is task considered internal? (hidden on :tasks list) |
become in TaskDeclaration (or commandline switch) |
become in TaskDeclaration (or commandline switch) |
become: root |
Change user for task execution time |
workdir in TaskDeclaration |
workdir in TaskDeclaration |
workdir: /some/path |
Change working directory for task execution time |
configure_argparse(task: BaseClassNameTask, parser: ArgumentParser) |
configure_argparse(self, parser: ArgumentParser) |
arguments: {} |
Configure argparse.ArgumentParser object |