- More
- Working with tasks
More
Working with tasks
Architect components have the concept of a Task, similar to a cron job. Since Tasks are defined in a component in a similar way to Services, you can configure your Tasks with dependencies on other components, take secrets, or call a service defined in the same component.
This guide will walk through the process of creating a new component with a Task that exercises most of the features available to Tasks.
- Create an example container and script to run as a Task
- Defining a minimal Component with a Task, and execute it locally
- Updating the Component to improve local development experience
- Tasks with Secrets
- Tasks with Dependencies
Create a Script and Dockerfile for our Task
Before getting started with the components, create a script for your task, and a dockerfile so you can bake the script into an image.
% mkdir ./my-task
% cd ./my-task
% touch Dockerfile my-task.sh
The Dockerfile
:
FROM alpine:3.14
COPY my-task.sh /my-task.sh
CMD ["sh", "-c", "/my-task.sh"]
The my-task.sh
:
#!/usr/bin/env sh
echo "${GREETING:-Hello}, world!"
With those created, go ahead and build the docker image and execute it, just to verify it’s working as expected.
% docker build . -t my-task:latest -q
sha256:d7116f02d2220db058c3df25417894fae557ae55875c9abb5f14946aa6c9b8ee
% docker run my-task:latest
Hello, world!
% docker run -e GREETING=Hola my-task:latest
Hola, world!
You can see that when the task runs without the GREETING
environment variable,
it displayed the typical “Hello, world!”, but will provide a more customized
message when the GREETING
variable is provided. With the docker image working,
proceed with creating the Architect Component and Task.
Define a Minimal Component Task
In your my-task
directory, create an architect file to define your component:
touch architect.yml
Now you can create a Task in the component file just as you would a Service. In this case you’ll give the component a brief description, a blank schedule so it runs only when manually executed, and finally you provide the build context as the local directly. The Component Task docs cover all the nuances in the task definition, as do the Task Reference docs.
The architect.yml
:
name: my-task
description: A hello world task! 👋
tasks:
hello-world:
schedule: ""
build:
context: ./
You can validate the component to verify aren’t any mistakes:
% architect validate ./architect.yml
✅ my-task: /Users/test/development/my-task/architect.yml
To run this locally, you’ll need to link the component by running:
% architect link ./architect.yml
Successfully linked my-task to local system at /Users/test/development/my-task/architect.yml.
Now that the component is linked, you can deploy it locally using the
architect dev
command. Under the hood, this creates a docker-compose file
which will be used as the task is executed.
% architect dev my-task
...
And now you can execute the task locally:
% architect task:exec --local my-task hello-world
Running task my-task/hello-world:latest in the local architect environment...
Creating architect-task-hello-world-xhkltnus_run ...
Creating architect-task-hello-world-xhkltnus_run ... done
Hello, world!
Successfully ran task.
And there you have it; you’ve executed your task locally! But you may want to improve the component definition to reduce friction during development.
Local Task Development
Development generally requires rapid iteration, and needing to run dev
and
task:exec
each time you want to test can be a tedious experience. To alleviate
this, you can make use of the debug
feature to mount our source code when you
run the task without having to re-deploy. This can be achieved by updating the
component with a debug block, re-deploying (just the once) to update the
component configuration. You will only use a few of the available local
development features, but more info can be found in the
Local Configuration docs.
Now you can update the architect file with the debug block containing a volume mount that overrides the script in the container with the script from the local filesystem:
tasks:
hello-world:
schedule: ""
build:
context: ./
+ debug:
+ volumes:
+ src:
+ mount_path: /my-task.sh
+ host_path: ./my-task.sh
You will have to redeploy the component because you changed its definition, so go ahead and redeploy and execute it again to make sure everything is still working as expected.
% architect dev my-task
...
% architect task:exec --local my-task hello-world
...
Hello, world!
Excellent! With this version of your component, the my-task.sh
script is being
mounted into the container and used when the task is executed. You should make a
change to the script and re-execute the task to verify this:
Change the my-task.sh
file to include a waving emoji in the output:
#!/usr/bin/env sh
-echo "${GREETING:-Hello}, world!"
+echo "${GREETING:-Hello}, world! 👋"
Now you’ll see the emoji in the output when you execute the task again without re-registering or re-deploying the component:
% architect task:exec --local my-task hello-world
...
Hello, world! 👋
Tasks with Secrets
The task you created supports a GREETING
environment variable, so you can make
use of that as you deploy the component by setting a value as a secret. In
addition to adding the secrets
section to the component, you’ll also need to
declare the GREETING
environment variable on the task, and assign it to the
value given by the secret. More information about secrets can be found in the
Components Secrets docs and the
Secrets Reference docs.
The architect.yml
file:
name: my-task
description: A hello world task! 👋
+secrets:
+ greeting:
+ required: false
+ description: The greeting to use
+
tasks:
hello-world:
schedule: ""
build:
context: ./
+ environment:
+ GREETING: ${{ secrets.greeting }}
debug:
volumes:
src:
Now you can redeploy the component with a secret value, whose value will be used
when the task is executed. It’s important to understand that these secrets are
set at deploy time, so you cannot re-declare the GREETING
value later when you
execute the task.
% architect dev my-task -s greeting=Hola
...
% architect task:exec --local my-task hello-world
Hola, world! 👋
...
Tasks with Dependencies
As mentioned earlier, tasks can use the same facilities as services which also means a Task can depend on a Service, just as Services can depend on other Services. So you can expand the component to include a service for generating names to use in your task’s greeting.
To start, add a really simple node server in a file called server.js
, and make
a dedicated docker image.
% mkdir ./server
% touch ./server/server.js
% touch ./server/Dockerfile
The server/server.js
:
const http = require("http");
const names = ["April", "Katie", "Marlon", "Darnell", "Simon", "Lena", "Noah"];
const requestListener = function (req, res) {
const name = names[(names.length * Math.random()) | 0];
res.writeHead(200);
res.end(name);
};
const server = http.createServer(requestListener);
server.listen(9000);
The server/Dockerfile
:
FROM node:16-alpine3.11
COPY server.js /server.js
CMD ["node", "/server.js"]
The server will respond with just the name in the body, so you can update the
script to simply curl the server address provided by the API_URL
variable.
#!/usr/bin/env sh
-echo "${GREETING:-Hello}, world! 👋"
+echo "${GREETING:-Hello}, $(curl -s $API_URL)! 👋"
Now go ahead and add the service to the architect file, and then pass the URL for the service into the task using the environment variable referenced in the script:
required: false
description: The greeting to use
+services:
+ name-generator:
+ build:
+ context: ./server
+ interfaces:
+ main:
+ port: 9000
+
tasks:
hello-world:
schedule: ""
@@ -13,6 +24,7 @@ tasks:
context: ./
environment:
GREETING: ${{ secrets.greeting }}
+ API_URL: ${{ services.name-generator.interfaces.main.url }}
debug:
volumes:
src:
Great! Now you can deploy your changes.
% architect dev --detached my-task -s greeting=Hola
Building containers... done
Once the containers are running they will be accessible via the following urls:
http://localhost:50006/ => my-task-name-generator-0s8tot3o:9000
Starting containers...
And now when you execute the task again, you’ll see a greeting for a random name provided by the dependent service:
% architect task:exec --local my-task hello-world ... Hola, Katie! 👋