Loading search data...

TaskFile - Une belle alternative à Makefile

Publié le : 18 décembre 2021 | Mis à jour le : 22 janvier 2023

TaskFile est un outil d’exécution de tâches ou de build plus simple et plus facile à apprendre que, par exemple, GNU Make et consorts. Il peut être utilisé dans un pipeline CI/CD vu son universalité.

Installation de TaskFile

Le plus simple et d’utiliser curl :

sh -c "$(curl --location https://taskfile.dev/install.sh)" -- -d -b /usr/local/bin

Utilisation de TaskFile

Pour l’utiliser il faut créer un fichier au format Yaml se nommant Taskfile.yml. Je vous rassure pour le créer il suffit d’utiliser la commande suivante :

task --init

Qui contient ceci :

# https://taskfile.dev

version: '3'

vars:
  GREETING: Hello, World!

tasks:
  default:
    cmds:
      - echo "{{.GREETING}}"
    silent: true

Cet exemple contient les mots clés essentiels :

  • vars: permet de déclarer des variables qui sont ensuite utilisé entre accolade avec le nom précédé d’un point : {{.variable}}
  • default: la tache par défaut
  • cmds: les commandes à exécuter
  • silent masque la commande ou pas

Pour l’exécuter il suffit de taper task sans aucune option puisqu’elle contient une task default :

task
Hello, World!

Pour lister les taches, il suffit de taper task --list. Les taches listées ne sont que celles contenant un descripteur desc:. Cela permet de cacher certaines qui sont en par exemple dépendantes d’autres.

L’option --help comme toujours affiche toutes les options :

task --help
Usage: task [-ilfwvsd] [--init] [--list] [--force] [--watch] [--verbose] [--silent] [--dir] [--taskfile] [--dry] [--summary] [task...]

Runs the specified task(s). Falls back to the "default" task if no task name
was specified, or lists all tasks if an unknown task name was specified.

Example: 'task hello' with the following 'Taskfile.yml' file will generate an
'output.txt' file with the content "hello".

'''
version: '3'
tasks:
  hello:
    cmds:
      - echo "I am going to write a file named 'output.txt' now."
      - echo "hello" > output.txt
    generates:
      - output.txt
'''

Options:
  -c, --color             colored output. Enabled by default. Set flag to false or use NO_COLOR=1 to disable (default true)
  -C, --concurrency int   limit number tasks to run concurrently
  -d, --dir string        sets directory of execution
      --dry               compiles and prints tasks in the order that they would be run, without executing them
  -f, --force             forces execution even when the task is up-to-date
  -h, --help              shows Task usage
  -i, --init              creates a new Taskfile.yml in the current folder
  -l, --list              lists tasks with description of current Taskfile
  -o, --output string     sets output style: [interleaved|group|prefixed]
  -p, --parallel          executes tasks provided on command line in parallel
  -s, --silent            disables echoing
      --status            exits with non-zero exit code if any of the given tasks is not up-to-date
      --summary           show summary about a task
  -t, --taskfile string   choose which Taskfile to run. Defaults to "Taskfile.yml"
  -v, --verbose           enables verbose mode
      --version           show Task version
  -w, --watch             enables watch of the given task

Comme vous pouvez le voir c’est très complet. Le plus important est l’option est l’option -t permettant de spécifier un fichier de tâche n’ayant pas le nom par défaut Taskfile.yml.

Syntaxe de Taskfile.yml

La syntaxe est assez simple et le mieux pour apprendre est de prendre un exemple courant gérer une image docker.

version: '3'

vars:
  CONTAINER_IMAGE: artefacts.robert.local/python

tasks:
  build:
    desc: Construction de l'image Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}} -f Dockerfile .

Ici comme il n’y a pas de tache default, il faut indiquer son nom derrière task pour l’exécuter :

task
task: Task "default" not found

task --list
build:

task build
unable to prepare context: unable to evaluate symlinks in Dockerfile path: lstat /home/vagrant/Projets/perso/aws-blog/Dockerfile: no such file or directory
task: Failed to run task "build": exit status 1

Ça ne fonctionne pas normal, car il n’y pas de fichier Dockerfile dans le répertoire.

Ajoutons plus de taches avec par exemple une image python permettant de lancer les différents outils de test/coverage:

version: '3'

vars:
  CONTAINER_IMAGE: artefacts.robert.local/python
  CONTAINER_VERSION: 0.1

tasks:
  build:
    desc: Construction de l'image Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    desc: Execute le container
    cmds:
      - docker run --rm {{.CONTAINER_IMAGE}}

Mon Dockerfile nécessitant buildkit :

FROM python:3.9-slim as builder
RUN <<EOF
  set -o errexit
  apt-get update
  apt-get install --no-install-recommends build-essential -y
  apt-get clean
  rm -rf /var/lib/apt/lists/*
EOF

WORKDIR /src
COPY Pipfile ./
COPY pip.conf /root/.config/pip/pip.conf
RUN <<EOF
  echo "export PATH=/root/.local/bin:${PATH}" >> "${HOME}/.bashrc"
  pip install --no-cache-dir pipenv==2021.11.15
  pipenv lock && pipenv install --system --deploy --three
EOF

Le Pipfile:

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
flake8 = "==4.0.1"
greenlet = "==1.1.2"
Jinja2 = "==3.0.1"
selinux = "== 0.2.1"
tenacity = "==8.0.1"
yamllint = "==1.26.3"
twine = "==3.6.0"
coverage= "==6.1.2"
pytest = "==6.2.5"
pip-tools = "==6.4.0"
wheel = "==0.37.0"
setuptools = "==57.5.0"

[dev-packages]

[requires]
python_version = "3.9"

utilisations de variables d’environnements

Il est possible de faire appel à des variables d’environnement du type dotenv :

# .env
CONTAINER_IMAGE = artefacts.robert.local/python
# testing/.env
CONTAINER_VERSION = 0.1

Pour y accéder il suffit d’utiliser la clé env: et dotenv :

version: '3'

env:
  ENV: testing

dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']

tasks:
  build:
    desc: Construction de l'image Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    desc: Execute le container
    cmds:
      - docker run --rm {{.CONTAINER_IMAGE}}

Inclure d’autres fichiers Taskfile

Il suffit d’utiliser la clé include avec le chemin du fichier :

# Taskfile.yml
version: '3'

env:
  ENV: testing

dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']

includes:
  docker: ./DockerTasks.yml
# ./DockerTasks.yml
version: '3'

tasks:
  build:
    desc: Construction de l'image Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    desc: Execute le container
    cmds:
      - docker run --rm {{.CONTAINER_IMAGE}}

Par contre, cela va transformer l’appel.

task --list
task: Available tasks for this project:
* docker:build: Construction de l'image Docker
* docker:exec:  Execute le container

task docker:build

Un include peut être optionnel :

# Taskfile.yml
version: '3'

env:
  ENV: testing

dotenv: ['.env', '{{.ENV}}/.env.', '{{.HOME}}/.env']

includes:
  docker: ./DockerTasks.yml
  optional: true

Spécifier le répertoire de travail

L’option dir permet de définir le répertoire d’exécution d’une tache :

# ./DockerTasks.yml
version: '3'

tasks:
  build:
    desc: Construction de l'image Docker
    dir: Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    desc: Execute le container
    cmds:
      - docker run --rm {{.CONTAINER_IMAGE}}

J’ai créé un répertoire Docker et j’y ai déplacé le Dockerfile dedans. Cela permet de limiter rapidement ce qui est inclu dans l’image.

Rendre des taches dépendantes

On aimerait que la tache exec dépende de celle du build. Rien de plus simple ajoutons l’option deps:

# ./DockerTasks.yml
version: '3'

tasks:
  build:
    desc: Construction de l'image Docker
    dir: Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    deps: [build]
    desc: Execute le container
    cmds:
      - docker run --rm {{.CONTAINER_IMAGE}}

Lançons pour tester :

task docker:exec
task: [docker:build] docker build -t artefacts.robert.local/python:0.1 -f Dockerfile .
[+] Building 1.2s (2/3)
[+] Building 2.3s (2/3)

Pour lancer plusieurs taches dépendantes il faut les séparer par des virgules :

# ./DockerTasks.yml
version: '3'

tasks:
  build:
    desc: Construction de l'image Docker
    dir: Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    deps: [build]
    desc: Execute le container
    cmds:
      - docker run --rm {{.CONTAINER_IMAGE}}
  push:
    desc: Pousse l'image dans nexus
    deps: [build, exec]
    cmds:
      - docker login -u {{.ARTEFACT_USER}} -p {{.ARTEFACT_PASSWD}}
      - docker push {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}}

Un autre moyen de d’utiliser dans cmds des appels à d’autres taches :

# ./DockerTasks.yml
version: '3'

tasks:
  build:
    desc: Construction de l'image Docker
    dir: Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
  exec:
    desc: Execute le container
    cmds:
      - task: build
      - docker run --rm {{.CONTAINER_IMAGE}}
  push:
    desc: Pousse l'image dans nexus
    cmds:
      - task: build
      - task: exec
      - docker login -u {{.ARTEFACT_USER}} -p {{.ARTEFACT_PASSWD}}
      - docker push {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}}

variables dynamiques

Il est possible rendre dynamique des variables, en utilisant un appel à un shell script. Par exemple on aimerait taguer notre image avec le short commit git :

# ./DockerTasks.yml
version: '3'

tasks:
  build:
    desc: Construction de l'image Docker
    dir: Docker
    cmds:
      - docker build -t {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}} -f Dockerfile .
    vars:
      CONTAINER_VERSION:
        sh: git rev-parse --short HEAD
  exec:
    desc: Execute le container
    cmds:
      - task: build
      - docker run --rm {{.CONTAINER_IMAGE}}
  push:
    desc: Pousse l'image dans nexus
    cmds:
      - task: build
      - task: exec
      - docker login -u {{.ARTEFACT_USER}} -p {{.ARTEFACT_PASSWD}}
      - docker push {{.CONTAINER_IMAGE}}:{{.CONTAINER_VERSION}}

Un autre moyen est l’utilisation des arguments de tache avec l’utilisation de – derrière le nom de la tache.

version: '3'

tasks:
  pip:
    cmds:
      - pip {{.CLI_ARGS}}

L’appel est de ce type :

task pip -- 'install -r requirements.txt'

Variables spéciales

Taskfile possède quelques variables qui peuvent être utile.

OS pouvant prendre les valeurs : “windows”, “linux”, “darwin” ou “freebsd” ARCH pouvant prendre les valeurs : “386”, “amd64”, “arm” ou “s390x”

Exemple d’utilisation :

version: '3'

includes:
  build: ./Taskfile_{{OS}}.yml

Variables sur plusieurs lignes

Une syntaxe parfois utile pour les variables :

    vars:
      CONTENT: |
        foo
        bar        

Utilisation du système de templating Go

On peut aussi, un peu avec Ansible et Jinja utiliser le système de template Go.

version: '3'

tasks:
  print-date:
    cmds:
      - echo {{now | date "2006-01-02"}}

Conclusion

C’est un bel outil permettant de rendre bien des services et de mettre de la logique dans vos projets ou de simplifier les CI. Il fait partie de ma liste d’outils préférés désormais.

Si vous voulez voir des exemples d’utilisation il suffit de rechercher sur github comme celui-ci : ProfessorManhattan

Plus d’infos

Mots clés :

devops tutorials

Si vous avez apprécié cet article de blog, vous pouvez m'encourager à produire plus de contenu en m'offrant un café sur  Ko-Fi. Vous pouvez aussi passer votre prochaine commande sur amazon, sans que cela ne vous coûte plus cher, via  ce lien . Vous pouvez aussi partager le lien sur twitter ou Linkedin via les boutons ci-dessous. Je vous remercie pour votre soutien.

Autres Articles


Commentaires: