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 in execute()

  • 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')

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 extends rkd.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