Repository: falcosecurity/driverkit
Pull request #193 (2022-07-27 13:06:03)

chore: improve tests and readme
**What type of PR is this?**

/kind cleanup

<!--
Please remove the leading whitespace before the `/kind <>` you uncommented.
-->

**Any specific area of the project related to this PR?**

/area cmd
/area docs

**What this PR does / why we need it**:

The PR enhances test suite for cmd package, leveraging text.template golang package (the same we use for builder's template scripts!).

Basically, test cases output is now a template and is run against a dynamically loaded template data.
This means:
* no more need to add any new target to `cmd/testdata/completion-targets.txt`
* no more need to add any possible new architecture to `cmd/testdata` files
* no more need to add any added/removed cmd line flags to `cmd/testdata` files

Just updating a single file in `cmd/testdata/templates` will be enough!

Moreover, updated README splitting examples and builder info to different files.

Finally, actually print list of supported architectures and targets in driverkit help text.

**Which issue(s) this PR fixes**:

<!--
Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
If PR is `kind/failing-tests` or `kind/flaky-test`, please post the related issues/tests in a comment and do not use `Fixes`.
-->

Fixes #

**Special notes for your reviewer**:

**Does this PR introduce a user-facing change?**:

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below.
If the PR requires additional action from users switching to the new release, prepend the string "action required:".
For example, `action required: change the API interface of the rule engine`.
-->

```release-note
NONE
```


poiana: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/falcosecurity/driverkit/pull/193#" title="Author self-approved">FedeDP</a>*

The full list of commands accepted by this bot can be found [here](https://prow.falco.org/command-help?repo=falcosecurity%2Fdriverkit).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/falcosecurity/driverkit/blob/master/OWNERS)~~ [FedeDP]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/Example_configs.md b/Example_configs.md
new file mode 100644
index 0000000..f0660c7
--- /dev/null
+++ b/Example_configs.md
@@ -0,0 +1,296 @@
+# Example Configs
+
+## ubuntu
+Example configuration file to build both the Kernel module and eBPF probe for Ubuntu (works with any flavor!).
+
+```yaml
+kernelrelease: 5.0.0-1021-aws-5.0
+kernelversion: 24~18.04.1
+target: ubuntu
+output:
+  module: /tmp/falco-ubuntu-generic.ko
+  probe: /tmp/falco-ubuntu-generic.o
+driverversion: master
+```
+
+## ubuntu-generic
+Example configuration file to build both the Kernel module and eBPF probe for Ubuntu generic.
+
+```yaml
+kernelrelease: 4.15.0-72-generic
+kernelversion: 81
+target: ubuntu-generic
+output:
+  module: /tmp/falco-ubuntu-generic.ko
+  probe: /tmp/falco-ubuntu-generic.o
+driverversion: master
+```
+
+> **NOTE:** ubuntu-generic exists to retain backward compatibility only,
+> and should not be used in new configs.
+
+## ubuntu-aws
+
+Example configuration file to build both the Kernel module and eBPF probe for Ubuntu AWS.
+
+```yaml
+kernelrelease: 4.15.0-1057-aws
+kernelversion: 59
+target: ubuntu-aws
+output:
+  module: /tmp/falco-ubuntu-aws.ko
+  probe: /tmp/falco-ubuntu-aws.o
+driverversion: master
+```
+
+> **NOTE:** ubuntu-aws exists to retain backward compatibility only,
+> and should not be used in new configs.
+
+## centos 6
+
+```yaml
+kernelrelease: 2.6.32-754.14.2.el6.x86_64
+kernelversion: 1
+target: centos
+output:
+  module: /tmp/falco-centos6.ko
+driverversion: master
+```
+
+## centos 7
+
+```yaml
+kernelrelease: 3.10.0-957.12.2.el7.x86_64
+kernelversion: 1
+target: centos
+output:
+  module: /tmp/falco-centos7.ko
+driverversion: master
+```
+
+## centos 8
+
+```yaml
+kernelrelease: 4.18.0-147.5.1.el8_1.x86_64
+kernelversion: 1
+target: centos
+output:
+  module: /tmp/falco-centos8.ko
+driverversion: master
+```
+
+## amazonlinux
+
+```yaml
+kernelrelease: 4.14.26-46.32.amzn1.x86_64
+target: amazonlinux
+output:
+    module: /tmp/falco_amazonlinux_4.14.26-46.32.amzn1.x86_64.ko
+driverversion: master
+```
+
+## amazonlinux 2
+
+```yaml
+kernelrelease: 4.14.171-136.231.amzn2.x86_64
+target: amazonlinux2
+output:
+    module: /tmp/falco_amazonlinux2_4.14.171-136.231.amzn2.x86_64.ko
+    probe: /tmp/falco_amazonlinux2_4.14.171-136.231.amzn2.x86_64.o
+driverversion: master
+```
+
+## amazonlinux 2022
+
+```yaml
+kernelrelease: 5.10.96-90.460.amzn2022.x86_64
+target: amazonlinux2022
+output:
+    module: /tmp/falco_amazonlinux2022_5.10.96-90.460.amzn2022.x86_64.ko
+    probe: /tmp/falco_amazonlinux2022_5.10.96-90.460.amzn2022.x86_64.o
+driverversion: master
+```
+
+## debian
+
+Example configuration file to build both the Kernel module and eBPF probe for Debian.
+
+```yaml
+kernelrelease: 4.19.0-6-amd64
+kernelversion: 1
+output:
+  module: /tmp/falco-debian.ko
+  probe: /tmp/falco-debian.o
+target: debian
+driverversion: master
+```
+
+## flatcar
+
+Example configuration file to build both the Kernel module and eBPF probe for Flatcar.
+The Flatcar release version needs to be provided in the `kernelrelease` field instead of the kernel version.
+
+```yaml
+kernelrelease: 3185.0.0
+target: flatcar
+output:
+  module: /tmp/falco-flatcar-3185.0.0.ko
+  probe: /tmp/falco-flatcar-3185.0.0.o
+driverversion: master
+```
+
+## minikube
+Example configuration file to build both the Kernel module and eBPF probe for Minikube.
+```yaml
+kernelversion: 1_1.26.0
+kernelrelease: 5.10.57
+target: minikube
+architecture: amd64
+output:
+  module: /tmp/falco_minikube_5.10.57_1_1.26.0.ko
+  probe: /tmp/falco_minikube_5.10.57_1_1.26.0.o
+kernelconfigdata: Q09ORklHX0ZBTk9USUZZPXkKQ09ORklHX0t...
+```
+
+## archlinux
+
+Example configuration file to build both the Kernel module and eBPF probe for Archlinux.
+Note: archlinux target uses the [Arch Linux Archive](https://wiki.archlinux.org/title/Arch_Linux_Archive) to fetch
+all ever supported kernel releases.  
+For arm64, it uses an user-provided mirror, as no official mirror is available: http://tardis.tiny-vps.com/aarm/.  
+The mirror has been up and updated since 2015.
+
+```yaml
+kernelversion: 1
+kernelrelease: 4.20.3-arch1
+target: archlinux
+output:
+  module: /tmp/falco-arch.ko
+  probe: /tmp/falco-arch.o
+driverversion: master
+```
+
+## redhat 7
+
+```yaml
+kernelrelease: 3.10.0-1160.66.1.el7.x86_64
+target: redhat
+output:
+  module: /tmp/falco-redhat7.ko
+driverversion: master
+builderimage: registry.redhat.io/rhel7:rhel7_driverkit
+```
+The image used for this build was created with the following command:
+
+```bash
+docker build --build-arg rh_username=<username> --build-arg rh_password=<password> -t registry.redhat.io/rhel7:rhel7_driverkit -f Dockerfile.rhel7 .
+````
+| :warning: **Passing user credentials via command line**: Consider using `--secret` option! |
+|--------------------------------------------------------------------------------------------|
+
+and Dockerfile.rhel7:
+```bash
+FROM registry.redhat.io/rhel7
+
+ARG rh_username
+ARG rh_password
+
+RUN subscription-manager register --username $rh_username --password $rh_password --auto-attach
+
+RUN yum install gcc elfutils-libelf-devel make -y
+```
+| :warning: **Base image requires Redhat subscription to pull**:```docker login registry.redhat.io``` |
+|-----------------------------------------------------------------------------------------------------|
+
+## redhat 8
+
+```yaml
+kernelrelease: 4.18.0-372.9.1.el8.x86_64
+target: redhat
+output:
+  module: /tmp/falco-redhat8.ko
+  probe: /tmp/falco-redhat8.o
+driverversion: master
+builderimage: redhat/ubi8:rhel8_driverkit
+```
+
+The image used for this build was created with the following command:
+
+```bash
+docker build --build-arg rh_username=<username> --build-arg rh_password=<password> -t redhat/ubi8:rhel8_driverkit -f Dockerfile.rhel8 .
+````
+| :warning: **Passing user credentials via command line**: Consider using `--secret` option! |
+|--------------------------------------------------------------------------------------------|
+
+and Dockerfile.rhel8:
+```bash
+FROM redhat/ubi8
+
+ARG rh_username
+ARG rh_password
+
+RUN subscription-manager register --username $rh_username --password $rh_password --auto-attach
+
+RUN yum install gcc curl elfutils-libelf-devel kmod make \
+                llvm-toolset-0:12.0.1-1.module+el8.5.0+11871+08d0eab5.x86_64 cpio -y
+```
+
+## redhat 9
+
+```yaml
+kernelrelease: 5.14.0-70.13.1.el9_0.x86_64
+target: redhat
+output:
+  module: /tmp/falco-redhat9.ko
+  probe: /tmp/falco-redhat9.o
+driverversion: master
+builderimage: docker.io/redhat/ubi9:rhel9_driverkit
+```
+The image used for this build was created with the following command:
+
+```bash
+docker build -t docker.io/redhat/ubi9:rhel9_driverkit -f Dockerfile.rhel9 .
+````
+
+and Dockerfile.rhel9:
+```bash
+FROM docker.io/redhat/ubi9
+
+RUN yum install gcc elfutils-libelf-devel kmod make cpio llvm-toolset -y
+```
+| :exclamation: **subscription-manager does not work on RHEL9 containers**: Host must have a valid RHEL subscription |
+|--------------------------------------------------------------------------------------------------------------------|
+
+## vanilla
+
+In case of vanilla, you also need to pass the kernel config data in base64 format.
+
+In most systems you can get `kernelconfigdata`  by reading `/proc/config.gz`.
+
+```yaml
+kernelrelease: 5.5.2
+kernelversion: 1
+target: vanilla
+output:
+  module: /tmp/falco-vanilla.ko
+  probe: /tmp/falco-vanilla.o
+driverversion: 0de226085cc4603c45ebb6883ca4cacae0bd25b2
+```
+
+Now you can add the `kernelconfigdata` to the configuration file, to do so:
+
+```bash
+zcat /proc/config.gz| base64 -w0 | awk '{print "kernelconfigdata: " $1;}' >> /tmp/vanilla.yaml
+```
+
+The command above assumes that you saved the configuration file at `/tmp/vanilla.yaml`
+
+### Note
+
+Usually, building for a `vanilla` target requires more time.
+
+So, we suggest to increase the `driverkit` timeout (defaults to `60` seconds):
+
+```bash
+driverkit docker -c /tmp/vanilla.yaml --timeout=300
+```
\ No newline at end of file
diff --git a/README.md b/README.md
index 2748c6f..d78b850 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,9 @@
 [![Latest](https://img.shields.io/github/v/release/falcosecurity/driverkit?style=for-the-badge)](https://github.com/falcosecurity/driverkit/releases/latest)
 ![Architectures](https://img.shields.io/badge/ARCHS-x86__64%7Caarch64-blueviolet?style=for-the-badge)
 
-Status: **Under development**
-
 A command line tool that can be used to build the [Falco](https://github.com/falcosecurity/falco) kernel module and eBPF probe.
 
-## Usage
+## Glossary
 
 When you meet `kernelversion` that refers to the version you get executing `uname -v`:
 
@@ -25,6 +23,42 @@ uname -r
 4.15.0-1057-aws
 ```
 
+## Help
+
+By checking driverkit help, you can quickly discover info about:
+* Supported options
+* Supported commands
+* Supported architectures
+* Supported targets
+* Default options
+
+```
+driverkit help
+```
+
+## Architecture
+
+The target architecture is taken from runtime environment, but it can be overridden through `architecture` config.  
+Driverkit also supports cross building for arm64 using qemu from an x86_64 host.
+
+> **NOTE:** we could not automatically fetch correct architecture given a kernelrelease,
+> because some kernel names do not have any architecture suffix, namely Ubuntu ones.
+
+## Headers
+
+Driverkit has an internal logic to retrieve headers urls given a target and desired kernelrelease/kernelversion.  
+Unfortunately, the logic is quite hard to implement correctly for every supported target.   
+As of today, the preferred method is to instead use the `kernelurls` configuration param,  
+that allows to specify a list of headers.
+
+> **NOTE:** the internal headers fetching logic should be considered a fallback that will be, sooner or later, deprecated.  
+
+A solution to crawl all supported kernels by multiple distro was recently developed,  
+and it provides a json output with aforementioned `kernelheaders`: https://github.com/falcosecurity/kernel-crawler.  
+Json for supported architectures can be found at https://github.com/falcosecurity/kernel-crawler/tree/main/kernels.
+
+## How to use
+
 ### Against a Kubernetes cluster
 
 ```bash
@@ -37,7 +71,6 @@ driverkit kubernetes --output-module /tmp/falco.ko --kernelversion=81 --kernelre
 driverkit docker --output-module /tmp/falco.ko --kernelversion=81 --kernelrelease=4.15.0-72-generic --driverversion=master --target=ubuntu-generic
 ```
 
-
 ### Build using a configuration file
 
 Create a file named `ubuntu-aws.yaml` containing the following content:
@@ -63,461 +96,17 @@ driverkit docker -c ubuntu-aws.yaml
 It is possible to customize the kernel module name that is produced by Driverkit with the `moduledevicename` and `moduledrivername` options.
 In this context, the _device name_ is the prefix used for the devices in `/dev/`, while the _driver name_ is the kernel module name as reported by `modinfo` or `lsmod` once the module is loaded.
 
-## Supported architectures
-
-At the moment, driverkit supports:
-* amd64 (x86_64)
-* arm64 (aarch64)
-
-The architecture is taken from runtime environment, but it can be overridden through `architecture` config.  
-Driverkit also supports cross building for arm64 using qemu from an x86_64 host.  
-
-Note: we could not automatically fetch correct architecture because some kernel names do not have the `-$arch`, namely Ubuntu ones.
-
-## Supported targets
-
-### ubuntu-generic
-Example configuration file to build both the Kernel module and eBPF probe for Ubuntu generic.
-
-```yaml
-kernelrelease: 4.15.0-72-generic
-kernelversion: 81
-target: ubuntu-generic
-output:
-  module: /tmp/falco-ubuntu-generic.ko
-  probe: /tmp/falco-ubuntu-generic.o
-driverversion: master
-```
-
-### ubuntu-aws
-
-Example configuration file to build both the Kernel module and eBPF probe for Ubuntu AWS.
-
-```yaml
-kernelrelease: 4.15.0-1057-aws
-kernelversion: 59
-target: ubuntu-aws
-output:
-  module: /tmp/falco-ubuntu-aws.ko
-  probe: /tmp/falco-ubuntu-aws.o
-driverversion: master
-```
-
-### centos 6
-
-```yaml
-kernelrelease: 2.6.32-754.14.2.el6.x86_64
-kernelversion: 1
-target: centos
-output:
-  module: /tmp/falco-centos6.ko
-driverversion: master
-```
-
-### centos 7
-
-```yaml
-kernelrelease: 3.10.0-957.12.2.el7.x86_64
-kernelversion: 1
-target: centos
-output:
-  module: /tmp/falco-centos7.ko
-driverversion: master
-```
-
-### centos 8
-
-```yaml
-kernelrelease: 4.18.0-147.5.1.el8_1.x86_64
-kernelversion: 1
-target: centos
-output:
-  module: /tmp/falco-centos8.ko
-driverversion: master
-```
-
-### amazonlinux
-
-```yaml
-kernelrelease: 4.14.26-46.32.amzn1.x86_64
-target: amazonlinux
-output:
-    module: /tmp/falco_amazonlinux_4.14.26-46.32.amzn1.x86_64.ko
-driverversion: master
-```
-
-### amazonlinux 2
-
-```yaml
-kernelrelease: 4.14.171-136.231.amzn2.x86_64
-target: amazonlinux2
-output:
-    module: /tmp/falco_amazonlinux2_4.14.171-136.231.amzn2.x86_64.ko
-    probe: /tmp/falco_amazonlinux2_4.14.171-136.231.amzn2.x86_64.o
-driverversion: master
-```
-
-### amazonlinux 2022
-
-```yaml
-kernelrelease: 5.10.96-90.460.amzn2022.x86_64
-target: amazonlinux2022
-output:
-    module: /tmp/falco_amazonlinux2022_5.10.96-90.460.amzn2022.x86_64.ko
-    probe: /tmp/falco_amazonlinux2022_5.10.96-90.460.amzn2022.x86_64.o
-driverversion: master
-```
-
-### debian
-
-Example configuration file to build both the Kernel module and eBPF probe for Debian.
-
-```yaml
-kernelrelease: 4.19.0-6-amd64
-kernelversion: 1
-output:
-  module: /tmp/falco-debian.ko
-  probe: /tmp/falco-debian.o
-target: debian
-driverversion: master
-```
-
-### flatcar
-
-Example configuration file to build both the Kernel module and eBPF probe for Flatcar.
-The Flatcar release version needs to be provided in the `kernelrelease` field instead of the kernel version.
-
-```yaml
-kernelrelease: 3185.0.0
-target: flatcar
-output:
-  module: /tmp/falco-flatcar-3185.0.0.ko
-  probe: /tmp/falco-flatcar-3185.0.0.o
-driverversion: master
-```
-### minikube
-Example configuration file to build both the Kernel module and eBPF probe for Minikube.
-```yaml
-kernelversion: 1_1.26.0
-kernelrelease: 5.10.57
-target: minikube
-architecture: amd64
-output:
-  module: /tmp/falco_minikube_5.10.57_1_1.26.0.ko
-  probe: /tmp/falco_minikube_5.10.57_1_1.26.0.o
-kernelconfigdata: Q09ORklHX0ZBTk9USUZZPXkKQ09ORklHX0t...
-```
-
-### archlinux
-
-Example configuration file to build both the Kernel module and eBPF probe for Archlinux.
-Note: archlinux target uses the [Arch Linux Archive](https://wiki.archlinux.org/title/Arch_Linux_Archive) to fetch
-all ever supported kernel releases.  
-For arm64, it uses an user-provided mirror, as no official mirror is available: http://tardis.tiny-vps.com/aarm/.  
-The mirror has been up and updated since 2015.
-
-```yaml
-kernelversion: 1
-kernelrelease: 4.20.3-arch1
-target: archlinux
-output:
-  module: /tmp/falco-arch.ko
-  probe: /tmp/falco-arch.o
-driverversion: master
-```
-
-### redhat 7
-
-```yaml
-kernelrelease: 3.10.0-1160.66.1.el7.x86_64
-target: redhat
-output:
-  module: /tmp/falco-redhat7.ko
-driverversion: master
-builderimage: registry.redhat.io/rhel7:rhel7_driverkit
-```
-The image used for this build was created with the following command:
-
-```bash
-docker build --build-arg rh_username=<username> --build-arg rh_password=<password> -t registry.redhat.io/rhel7:rhel7_driverkit -f Dockerfile.rhel7 .
-````
-| :warning: **Passing user credentials via command line**: Consider using `--secret` option! |
-|--------------------------------------------------------------------------------------------|
-
-and Dockerfile.rhel7:
-```bash
-FROM registry.redhat.io/rhel7
-
-ARG rh_username
-ARG rh_password
-
-RUN subscription-manager register --username $rh_username --password $rh_password --auto-attach
-
-RUN yum install gcc elfutils-libelf-devel make -y
-```
-| :warning: **Base image requires Redhat subscription to pull**:```docker login registry.redhat.io``` |
-|-----------------------------------------------------------------------------------------------------|
-### redhat 8
-
-```yaml
-kernelrelease: 4.18.0-372.9.1.el8.x86_64
-target: redhat
-output:
-  module: /tmp/falco-redhat8.ko
-  probe: /tmp/falco-redhat8.o
-driverversion: master
-builderimage: redhat/ubi8:rhel8_driverkit
-```
-
-The image used for this build was created with the following command:
-
-```bash
-docker build --build-arg rh_username=<username> --build-arg rh_password=<password> -t redhat/ubi8:rhel8_driverkit -f Dockerfile.rhel8 .
-````
-| :warning: **Passing user credentials via command line**: Consider using `--secret` option! |
-|--------------------------------------------------------------------------------------------|
-
-and Dockerfile.rhel8:
-```bash
-FROM redhat/ubi8
-
-ARG rh_username
-ARG rh_password
+## Examples
 
-RUN subscription-manager register --username $rh_username --password $rh_password --auto-attach
+For a comprehensive list of examples, heads to [example configs](Example_configs.md)!
 
-RUN yum install gcc curl elfutils-libelf-devel kmod make \
-                llvm-toolset-0:12.0.1-1.module+el8.5.0+11871+08d0eab5.x86_64 cpio -y
-```
-
-### redhat 9
-
-```yaml
-kernelrelease: 5.14.0-70.13.1.el9_0.x86_64
-target: redhat
-output:
-  module: /tmp/falco-redhat9.ko
-  probe: /tmp/falco-redhat9.o
-driverversion: master
-builderimage: docker.io/redhat/ubi9:rhel9_driverkit
-```
-The image used for this build was created with the following command:
-
-```bash
-docker build -t docker.io/redhat/ubi9:rhel9_driverkit -f Dockerfile.rhel9 .
-````
-
-and Dockerfile.rhel9:
-```bash
-FROM docker.io/redhat/ubi9
-
-RUN yum install gcc elfutils-libelf-devel kmod make cpio llvm-toolset -y
-```
-| :exclamation: **subscription-manager does not work on RHEL9 containers**: Host must have a valid RHEL subscription |
-|--------------------------------------------------------------------------------------------------------------------|
-
-### vanilla
-
-In case of vanilla, you also need to pass the kernel config data in base64 format.
-
-In most systems you can get `kernelconfigdata`  by reading `/proc/config.gz`.
-
-```yaml
-kernelrelease: 5.5.2
-kernelversion: 1
-target: vanilla
-output:
-  module: /tmp/falco-vanilla.ko
-  probe: /tmp/falco-vanilla.o
-driverversion: 0de226085cc4603c45ebb6883ca4cacae0bd25b2
-```
-
-Now you can add the `kernelconfigdata` to the configuration file, to do so:
-
-```bash
-zcat /proc/config.gz| base64 -w0 | awk '{print "kernelconfigdata: " $1;}' >> /tmp/vanilla.yaml
-```
+## Support a new target
 
-The command above assumes that you saved the configuration file at `/tmp/vanilla.yaml`
-
-#### Note
-
-Usually, building for a `vanilla` target requires more time.
-
-So, we suggest to increase the `driverkit` timeout (defaults to `60` seconds):
-
-```bash
-driverkit docker -c /tmp/vanilla.yaml --timeout=300
-```
-
-## Goals
-
-- [x] Have a package that can build the Falco kernel module in k8s
-- [x] Have a package that can build the Falco kernel module in docker
-- [x] Have a package that can build the Falco eBPF probe in k8s
-- [x] Have a package that can build the Falco eBPF probe in docker
-- [x] Support the top distributions in our [Survey](http://bit.ly/driverkit-survey-vote) and the Vanilla Kernel
-  - [x] Ubuntu (`ubuntu-aws`, `ubuntu-generic`)
-  - [x] CentOS 8
-  - [x] CentOS 7
-  - [x] CentOS 6
-  - [x] AmazonLinux (`amazonlinux`, `amazonlinux2`)
-  - [x] Debian
-  - [x] Vanilla kernel (`vanilla`)
+To add support for a new target, a new builder must be added.  
+For more info, you can find specific docs under [pkg/driverbuilder/builder](/pkg/driverbuilder/builder) README file.
 
 ## Survey
 
 We are conducting a [survey](http://bit.ly/driverkit-survey-vote) to know what is the most interesting set of Operating Systems we must support first in driverkit.
 
-You can find the results of the survey [here](http://bit.ly/driverkit-survey-results)
-
-## Creating a new Builder
-
-You probably came here because you want to tell the [Falco Drivers Build Grid](https://github.com/falcosecurity/test-infra/tree/master/driverkit) to
-build drivers for a specific distro you care about.
-
-If that distribution is not supported by driverkit, the Falco Drivers Build Grid will not be able to just build it as it does for other distros.
-
-To add a new supported distribution, you need to create a specific file implementing the `builder.Builder` interface.
-
-You can find the specific distribution files into the [pkg/driverbuilder/builder](/pkg/driverbuilder/builder) folder.
-
-Here's the [Ubuntu](/pkg/driverbuilder/builder/ubuntu.go) one for reference.
-
-Following this simple set of instructions should help you while you implement a new `builder.Builder`.
-
-
-### 1. Builder file
-Create a file, named with the name of the distro you want to add in the `pkg/driverbuilder/builder` folder.
-
-```bash
-touch pkg/driverbuilder/builder/archlinux.go
-```
-
-### 2. Target name
-
-Your builder will need a constant for the target it implements. Usually that constant
-can just be the name of the distribution you are implementing. A builder can implement 
-more than one target at time. For example, the Ubuntu builder implements both `ubuntu-generic` and `ubuntu-aws`
-to reflect the organization that the distro itself has.
-
-Once you have the constant, you will need to add it to the `BuilderByTarget` map.
-
-
-Open your file and you will need to have something like this:
-
-```go
-// TargetTypeArchLinux identifies the Arch Linux target.
-const TargetTypeArchLinux Type = "archlinux"
-
-type archLinux struct {
-}
-
-func init() {
-	BuilderByTarget[TargetTypeArchLinux] = &archLinux{}
-}
-```
-
-Now, you can implement the `builder.Builder` interface for the `archlinux` struct
-you just registered.
-
-Here's a very minimalistic example:
-
-```go
-func (c archlinux) Name() string {
-    return TargetTypeArchlinux.String()
-}
-
-func (c archlinux) TemplateScript() string {
-	return archlinuxTemplate
-}
-
-func (c archlinux) URLs(cfg Config, kr kernelrelease.KernelRelease) ([]string, error) {
-    urls := []string{}
-    if kr.Architecture == "amd64" {
-        urls = append(urls, fmt.Sprintf("https://archive.archlinux.org/packages/l/linux-headers/linux-headers-%s.%s-%d-%s.pkg.tar.xz",
-            kr.Fullversion,
-            kr.Extraversion,
-            cfg.KernelVersion,
-            kr.Architecture.ToNonDeb()))
-    } else {
-        urls = append(urls, fmt.Sprintf("http://tardis.tiny-vps.com/aarm/packages/l/linux-%s-headers/linux-%s-headers-%s-%d-%s.pkg.tar.xz",
-            kr.Architecture.ToNonDeb(),
-            kr.Architecture.ToNonDeb(),
-            kr.Fullversion,
-            cfg.KernelVersion,
-            kr.Architecture.ToNonDeb()))
-    }
-    return urls, nil
-}
-
-func (c archlinux) TemplateData(cfg Config, kr kernelrelease.KernelRelease, urls []string) interface{} {
-    return archlinuxTemplateData{
-        commonTemplateData: cfg.toTemplateData(),
-        KernelDownloadURL:  urls[0],
-        GCCVersion:         archlinuxGccVersionFromKernelRelease(kr),
-    }
-}
-```
-
-Essentially, the various methods that you are implementing are needed to:
-* filling the script template (see below), that is a `bash` script that will be executed by driverkit at build time 
-* fetching kernel headers urls that will later be downloaded inside the builder container, and used for the driver build
-
-Under `pkg/driverbuilder/builder/templates` folder, you can find all the template scripts for the supported builders.  
-Adding a new template there and using `go:embed` to include it in your builder, allows leaner code 
-without mixing up templates and builder logic.  
-For example:
-
-```go
-//go:embed templates/archlinux.sh
-var archlinuxTemplate string
-```
-
-Depending on how the distro works, the script will need to fetch the kernel headers for it at the specific kernel version specified
-in the `Config` struct at `c.Build.KernelVersion`.
-Once you have those, based on what that kernel can do and based on what was configured
-by the user you will need to build the kernel module driver and/or the eBPF probe driver.
-
-How does this work?
-
-If the user specifies:
-
-- `c.Build.ModuleFilePath` you will need to build the kernel module and save it in /tmp/driver/falco.ko`
-- `c.Build.ProbeFilePath` you will need to build the eBPF probe and save it in /tmp/driver/probe.o`
-
-The `/tmp/driver` MUST be interpolated from the `DriverDirectory` constant from [`builders.go`](/pkg/driverbuilder/builder/builders.go).
-
-If you look at the various builder implemented, you will see that the task of creating a new builder
-can be easy or difficult depending on how the distribution ships their artifacts.  
-Indeed, the hardest part is fetching the kernel headers urls for each distro. 
-
-### 3. Customize GCC version
-
-Driverkit builder image supports 4 gcc versions:
-* GCC-8
-* GCC-6.3.0
-* GCC-5.5.0
-* GCC-4.8.4
-
-You can dynamically choose the one you prefer, likely switching on the kernel version.  
-For an example, you can check out Ubuntu builder, namely: `ubuntuGCCVersionFromKernelRelease`.  
-
-### 4. Customize llvm version
-
-Driverkit builder image supports 2 llvm versions:
-* llvm-7
-* llvm-12
-
-You can dynamically choose the one you prefer, likely switching on the kernel version.  
-For an example, you can check out Debian builder, namely: `debianLLVMVersionFromKernelRelease`.
-
-### 5. kernel-crawler
-
-When creating a new builder, it is recommended to check that [kernel-crawler](https://github.com/falcosecurity/kernel-crawler) 
-can also support collecting the new builders kernel versions and header package URLs. This will make sure that the latest drivers 
-for the new builder are automatically built by [test-infra](https://github.com/falcosecurity/test-infra). If required, add a feature request
-for support for the new builder on the kernel-crawler repository.  
-Note: be sure that the crawler you wants to add is interesting for the community as a whole.  
-For example, an archlinux crawler doesn't make much sense, because Arch is a rolling release and we should not support  
-any past Arch kernel for Falco.
+You can find the results of the survey [here](http://bit.ly/driverkit-survey-results)
\ No newline at end of file
diff --git a/cmd/root.go b/cmd/root.go
index 9a6d918..6327c32 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -2,6 +2,7 @@ package cmd
 
 import (
 	"fmt"
+	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"io"
 	"os"
 	"runtime"
@@ -37,34 +38,34 @@ func persistentValidateFunc(rootCommand *RootCmd, rootOpts *RootOptions) func(c
 			"output-probe":  "output.probe",
 		}
 		rootCommand.c.Flags().VisitAll(func(f *pflag.Flag) {
-		    if name := f.Name; !skip[name] {
-                if name == "kernelurls" {
-                    // Slice types need special treatment when used as flags. If we call 'Set(name, value)',
-                    // rather than replace, it appends. Since viper will already have the cli options set
-                    // if supplied, we only need this step if rootCommand doesn't already have them e.g.
-                    // not set on CLI so read from config.
-                    if cli_urls, err := rootCommand.c.Flags().GetStringSlice(name); err == nil && len(cli_urls) != 0 {
-                       return
-                    }
-                    value := viper.GetStringSlice(name)
-                    if len(value) != 0 {
-                        strValue := strings.Join(value, ",")
-                        rootCommand.c.Flags().Set(name, strValue)
-                    }
-                } else {
-                    value := viper.GetString(name)
-                    if value == "" {
-                        // fallback to nested options in config file, if any
-                        if nestedName, ok := nested[name]; ok {
-                            value = viper.GetString(nestedName)
-                        }
-                    }
-                    // set the value, if any, otherwise let the default
-                    if value != "" {
-                        rootCommand.c.Flags().Set(name, value)
-                    }
-                }
-            }
+			if name := f.Name; !skip[name] {
+				if name == "kernelurls" {
+					// Slice types need special treatment when used as flags. If we call 'Set(name, value)',
+					// rather than replace, it appends. Since viper will already have the cli options set
+					// if supplied, we only need this step if rootCommand doesn't already have them e.g.
+					// not set on CLI so read from config.
+					if cli_urls, err := rootCommand.c.Flags().GetStringSlice(name); err == nil && len(cli_urls) != 0 {
+						return
+					}
+					value := viper.GetStringSlice(name)
+					if len(value) != 0 {
+						strValue := strings.Join(value, ",")
+						rootCommand.c.Flags().Set(name, strValue)
+					}
+				} else {
+					value := viper.GetString(name)
+					if value == "" {
+						// fallback to nested options in config file, if any
+						if nestedName, ok := nested[name]; ok {
+							value = viper.GetString(nestedName)
+						}
+					}
+					// set the value, if any, otherwise let the default
+					if value != "" {
+						rootCommand.c.Flags().Set(name, value)
+					}
+				}
+			}
 		})
 
 		// Avoid sensitive info into default values help line
@@ -118,6 +119,9 @@ func NewRootCmd() *RootCmd {
 
 	flags := rootCmd.Flags()
 
+	targets := builder.BuilderByTarget.Targets()
+	sort.Strings(targets)
+
 	flags.StringVarP(&configOptions.ConfigFile, "config", "c", configOptions.ConfigFile, "config file path (default $HOME/.driverkit.yaml if exists)")
 	flags.StringVarP(&configOptions.LogLevel, "loglevel", "l", configOptions.LogLevel, "log level")
 	flags.IntVar(&configOptions.Timeout, "timeout", configOptions.Timeout, "timeout in seconds")
@@ -126,11 +130,11 @@ func NewRootCmd() *RootCmd {
 
 	flags.StringVar(&rootOpts.Output.Module, "output-module", rootOpts.Output.Module, "filepath where to save the resulting kernel module")
 	flags.StringVar(&rootOpts.Output.Probe, "output-probe", rootOpts.Output.Probe, "filepath where to save the resulting eBPF probe")
-	flags.StringVar(&rootOpts.Architecture, "architecture", runtime.GOARCH, "target architecture for the built driver")
+	flags.StringVar(&rootOpts.Architecture, "architecture", runtime.GOARCH, "target architecture for the built driver, one of "+kernelrelease.SupportedArchs.String())
 	flags.StringVar(&rootOpts.DriverVersion, "driverversion", rootOpts.DriverVersion, "driver version as a git commit hash or as a git tag")
 	flags.StringVar(&rootOpts.KernelVersion, "kernelversion", rootOpts.KernelVersion, "kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v'")
 	flags.StringVar(&rootOpts.KernelRelease, "kernelrelease", rootOpts.KernelRelease, "kernel release to build the module for, it can be found by executing 'uname -v'")
-	flags.StringVarP(&rootOpts.Target, "target", "t", rootOpts.Target, "the system to target the build for")
+	flags.StringVarP(&rootOpts.Target, "target", "t", rootOpts.Target, "the system to target the build for, one of ["+strings.Join(targets, ",")+"]")
 	flags.StringVar(&rootOpts.KernelConfigData, "kernelconfigdata", rootOpts.KernelConfigData, "base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc")
 	flags.StringVar(&rootOpts.ModuleDeviceName, "moduledevicename", rootOpts.ModuleDeviceName, "kernel module device name (the default is falco, so the device will be under /dev/falco*)")
 	flags.StringVar(&rootOpts.ModuleDriverName, "moduledrivername", rootOpts.ModuleDriverName, "kernel module driver name, i.e. the name you see when you check installed modules via lsmod")
@@ -142,10 +146,11 @@ func NewRootCmd() *RootCmd {
 	// Flag annotations and custom completions
 	rootCmd.MarkFlagFilename("config", viper.SupportedExts...)
 	rootCmd.RegisterFlagCompletionFunc("target", func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		targets := builder.BuilderByTarget.Targets()
-		sort.Strings(targets)
 		return targets, cobra.ShellCompDirectiveDefault
 	})
+	rootCmd.RegisterFlagCompletionFunc("architecture", func(c *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		return kernelrelease.SupportedArchs.Strings(), cobra.ShellCompDirectiveDefault
+	})
 
 	// Subcommands
 	rootCmd.AddCommand(NewKubernetesCmd(rootOpts, flags))
diff --git a/cmd/root_options.go b/cmd/root_options.go
index e2d11fb..d88709e 100644
--- a/cmd/root_options.go
+++ b/cmd/root_options.go
@@ -19,7 +19,7 @@ type OutputOptions struct {
 
 // RootOptions ...
 type RootOptions struct {
-	Architecture     string   `validate:"required,oneof=amd64 arm64" name:"architecture"`
+	Architecture     string   `validate:"required,architecture" name:"architecture"`
 	DriverVersion    string   `default:"master" validate:"eq=master|sha1|semver" name:"driver version"`
 	KernelVersion    string   `default:"1" validate:"omitempty" name:"kernel version"`
 	ModuleDriverName string   `default:"falco" validate:"max=60" name:"kernel module driver name"`
diff --git a/pkg/driverbuilder/builder/README.md b/pkg/driverbuilder/builder/README.md
new file mode 100644
index 0000000..85e2669
--- /dev/null
+++ b/pkg/driverbuilder/builder/README.md
@@ -0,0 +1,153 @@
+# Builders
+
+This folder contains all of the actual builders code, for each supported target.  
+Their bash-like build templates live in the [templates](templates) subfolder.
+
+## Creating a new Builder
+
+You probably came here because you want to tell the [Falco Drivers Build Grid](https://github.com/falcosecurity/test-infra/tree/master/driverkit) to
+build drivers for a specific distro you care about.
+
+If that distribution is not supported by driverkit, the Falco Drivers Build Grid will not be able to just build it as it does for other distros.
+
+To add a new supported distribution, you need to create a specific file implementing the `builder.Builder` interface.
+
+Here's the [archlinux](archlinux.go) one for reference.
+
+Following this simple set of instructions should help you while you implement a new `builder.Builder`.
+
+
+### 1. Builder file
+Create a file, named with the name of the distro you want to add in the `pkg/driverbuilder/builder` folder.
+
+```bash
+touch pkg/driverbuilder/builder/archlinux.go
+```
+
+### 2. Target name
+
+Your builder will need a constant for the target it implements. Usually that constant
+can just be the name of the distribution you are implementing. A builder can implement
+more than one target at time. For example, the Ubuntu builder implements both `ubuntu-generic` and `ubuntu-aws`
+to reflect the organization that the distro itself has.
+
+Once you have the constant, you will need to add it to the `BuilderByTarget` map.
+
+
+Open your file and you will need to have something like this:
+
+```go
+// TargetTypeArchLinux identifies the Arch Linux target.
+const TargetTypeArchLinux Type = "archlinux"
+
+type archLinux struct {
+}
+
+func init() {
+	BuilderByTarget[TargetTypeArchLinux] = &archLinux{}
+}
+```
+
+Now, you can implement the `builder.Builder` interface for the `archlinux` struct
+you just registered.
+
+Here's a very minimalistic example:
+
+```go
+func (c archlinux) Name() string {
+    return TargetTypeArchlinux.String()
+}
+
+func (c archlinux) TemplateScript() string {
+	return archlinuxTemplate
+}
+
+func (c archlinux) URLs(cfg Config, kr kernelrelease.KernelRelease) ([]string, error) {
+    urls := []string{}
+    if kr.Architecture == "amd64" {
+        urls = append(urls, fmt.Sprintf("https://archive.archlinux.org/packages/l/linux-headers/linux-headers-%s.%s-%d-%s.pkg.tar.xz",
+            kr.Fullversion,
+            kr.Extraversion,
+            cfg.KernelVersion,
+            kr.Architecture.ToNonDeb()))
+    } else {
+        urls = append(urls, fmt.Sprintf("http://tardis.tiny-vps.com/aarm/packages/l/linux-%s-headers/linux-%s-headers-%s-%d-%s.pkg.tar.xz",
+            kr.Architecture.ToNonDeb(),
+            kr.Architecture.ToNonDeb(),
+            kr.Fullversion,
+            cfg.KernelVersion,
+            kr.Architecture.ToNonDeb()))
+    }
+    return urls, nil
+}
+
+func (c archlinux) TemplateData(cfg Config, kr kernelrelease.KernelRelease, urls []string) interface{} {
+    return archlinuxTemplateData{
+        commonTemplateData: cfg.toTemplateData(),
+        KernelDownloadURL:  urls[0],
+        GCCVersion:         archlinuxGccVersionFromKernelRelease(kr),
+    }
+}
+```
+
+Essentially, the various methods that you are implementing are needed to:
+* filling the script template (see below), that is a `bash` script that will be executed by driverkit at build time
+* fetching kernel headers urls that will later be downloaded inside the builder container, and used for the driver build
+
+Under `pkg/driverbuilder/builder/templates` folder, you can find all the template scripts for the supported builders.  
+Adding a new template there and using `go:embed` to include it in your builder, allows leaner code
+without mixing up templates and builder logic.  
+For example:
+
+```go
+//go:embed templates/archlinux.sh
+var archlinuxTemplate string
+```
+
+Depending on how the distro works, the script will need to fetch the kernel headers for it at the specific kernel version specified
+in the `Config` struct at `c.Build.KernelVersion`.
+Once you have those, based on what that kernel can do and based on what was configured
+by the user you will need to build the kernel module driver and/or the eBPF probe driver.
+
+How does this work?
+
+If the user specifies:
+
+- `c.Build.ModuleFilePath` you will need to build the kernel module and save it in /tmp/driver/falco.ko`
+- `c.Build.ProbeFilePath` you will need to build the eBPF probe and save it in /tmp/driver/probe.o`
+
+The `/tmp/driver` MUST be interpolated from the `DriverDirectory` constant from [`builders.go`](/pkg/driverbuilder/builder/builders.go).
+
+If you look at the various builder implemented, you will see that the task of creating a new builder
+can be easy or difficult depending on how the distribution ships their artifacts.  
+Indeed, the hardest part is fetching the kernel headers urls for each distro.
+
+### 3. Customize GCC version
+
+Driverkit builder image supports 4 gcc versions:
+* GCC-8
+* GCC-6.3.0
+* GCC-5.5.0
+* GCC-4.8.4
+
+You can dynamically choose the one you prefer, likely switching on the kernel version.  
+For an example, you can check out Ubuntu builder, namely: `ubuntuGCCVersionFromKernelRelease`.
+
+### 4. Customize llvm version
+
+Driverkit builder image supports 2 llvm versions:
+* llvm-7
+* llvm-12
+
+You can dynamically choose the one you prefer, likely switching on the kernel version.  
+For an example, you can check out Debian builder, namely: `debianLLVMVersionFromKernelRelease`.
+
+### 5. kernel-crawler
+
+When creating a new builder, it is recommended to check that [kernel-crawler](https://github.com/falcosecurity/kernel-crawler)
+can also support collecting the new builders kernel versions and header package URLs. This will make sure that the latest drivers
+for the new builder are automatically built by [test-infra](https://github.com/falcosecurity/test-infra). If required, add a feature request
+for support for the new builder on the kernel-crawler repository.  
+Note: be sure that the crawler you wants to add is interesting for the community as a whole.  
+For example, an archlinux crawler doesn't make much sense, because Arch is a rolling release and we should not support  
+any past Arch kernel for Falco.
\ No newline at end of file
diff --git a/pkg/driverbuilder/builder/centos.go b/pkg/driverbuilder/builder/centos.go
index 30de8f3..dce11a3 100644
--- a/pkg/driverbuilder/builder/centos.go
+++ b/pkg/driverbuilder/builder/centos.go
@@ -105,6 +105,11 @@ func (c centos) URLs(_ Config, kr kernelrelease.KernelRelease) ([]string, error)
 		"8-stream/BaseOS",
 	}
 
+	stream9Releases := []string{
+		"9-stream/AppStream",
+		"9-stream/BaseOS",
+	}
+
 	urls := []string{}
 	for _, r := range edgeReleases {
 		urls = append(urls, fmt.Sprintf(
@@ -142,6 +147,17 @@ func (c centos) URLs(_ Config, kr kernelrelease.KernelRelease) ([]string, error)
 			kr.FullExtraversion,
 		))
 	}
+
+	for _, r := range stream9Releases {
+		urls = append(urls, fmt.Sprintf(
+			"http://mirror.stream.centos.org/%s/%s/os/Packages/kernel-devel-%s%s.rpm",
+			r,
+			kr.Architecture.ToNonDeb(),
+			kr.Fullversion,
+			kr.FullExtraversion,
+		))
+	}
+
 	return urls, nil
 }
 
diff --git a/pkg/kernelrelease/kernelrelease.go b/pkg/kernelrelease/kernelrelease.go
index bb5edb7..89029c1 100644
--- a/pkg/kernelrelease/kernelrelease.go
+++ b/pkg/kernelrelease/kernelrelease.go
@@ -1,25 +1,53 @@
 package kernelrelease
 
 import (
+	"fmt"
 	"log"
 	"regexp"
 	"strconv"
+	"strings"
 )
 
 var (
 	kernelVersionPattern = regexp.MustCompile(`(?P<fullversion>^(?P<version>0|[1-9]\d*)\.(?P<patchlevel>0|[1-9]\d*)\.(?P<sublevel>0|[1-9]\d*))(?P<fullextraversion>-(?P<extraversion>0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-_]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$`)
 )
 
+// Architectures is a Map [Architecture] -> non-deb-ArchitectureString
+type Architectures map[Architecture]string
+
+// SupportedArchs enforces the duality of architecture->non-deb one when adding a new one
+var SupportedArchs = Architectures{
+	"amd64": "x86_64",
+	"arm64": "aarch64",
+}
+
+// Privately cached at startup for quicker access
+var supportedArchsSlice []string
+
+func init() {
+	i := 0
+	supportedArchsSlice = make([]string, len(SupportedArchs))
+	for k := range SupportedArchs {
+		supportedArchsSlice[i] = k.String()
+		i++
+	}
+}
+
+func (aa Architectures) String() string {
+	return "[" + strings.Join(supportedArchsSlice, ",") + "]"
+}
+
+func (aa Architectures) Strings() []string {
+	return supportedArchsSlice
+}
+
 type Architecture string
 
 func (a Architecture) ToNonDeb() string {
-	switch a {
-	case "arm64":
-		return "aarch64"
-	case "amd64":
-		return "x86_64"
+	if val, ok := SupportedArchs[a]; ok {
+		return val
 	}
-	return ""
+	panic(fmt.Errorf("missing non-deb name for arch: %s", a.String()))
 }
 
 func (a Architecture) String() string {
diff --git a/validate/architecture.go b/validate/architecture.go
new file mode 100644
index 0000000..afdddb6
--- /dev/null
+++ b/validate/architecture.go
@@ -0,0 +1,24 @@
+package validate
+
+import (
+	"fmt"
+	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
+	"github.com/go-playground/validator/v10"
+	"reflect"
+)
+
+func isArchitectureSupported(fl validator.FieldLevel) bool {
+	field := fl.Field()
+
+	switch field.Kind() {
+	case reflect.String:
+		for arch := range kernelrelease.SupportedArchs {
+			if arch.String() == field.String() {
+				return true
+			}
+		}
+		return false
+	}
+
+	panic(fmt.Sprintf("Bad field type %T", field.Interface()))
+}
diff --git a/validate/validate.go b/validate/validate.go
index 3d0f798..d68aeb5 100644
--- a/validate/validate.go
+++ b/validate/validate.go
@@ -2,6 +2,7 @@ package validate
 
 import (
 	"fmt"
+	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"reflect"
 	"strings"
 
@@ -36,6 +37,7 @@ func init() {
 	V.RegisterValidation("filepath", isFilePath)
 	V.RegisterValidation("sha1", isSHA1)
 	V.RegisterValidation("target", isTargetSupported)
+	V.RegisterValidation("architecture", isArchitectureSupported)
 	V.RegisterValidation("semver", isSemVer)
 	V.RegisterValidation("proxy", isProxy)
 	V.RegisterValidation("imagename", isImageName)
@@ -71,6 +73,19 @@ func init() {
 		},
 	)
 
+	V.RegisterTranslation(
+		"architecture",
+		T,
+		func(ut ut.Translator) error {
+			return ut.Add("architecture", fmt.Sprintf("{0} must be a valid architecture (%s)", kernelrelease.SupportedArchs.String()), true)
+		},
+		func(ut ut.Translator, fe validator.FieldError) string {
+			t, _ := ut.T(fe.Tag(), fe.Field())
+
+			return t
+		},
+	)
+
 	V.RegisterTranslation(
 		"required_kernelconfigdata_with_target_vanilla",
 		T,
@@ -98,17 +113,17 @@ func init() {
 	)
 
 	V.RegisterTranslation(
-	    "required_builderimage_with_target_redhat",
-	    T,
-	    func(ut ut.Translator) error {
-	        return ut.Add("required_builderimage_with_target_redhat", "{0} is a required field when target is redhat", true)
-	    },
-	    func(ut ut.Translator, fe validator.FieldError) string {
-	        t, _ := ut.T("required_builderimage_with_target_redhat", "builder image") // fixme ? tag "name" does not work when used at struct level
-
-    		return t
-    	},
-    )
+		"required_builderimage_with_target_redhat",
+		T,
+		func(ut ut.Translator) error {
+			return ut.Add("required_builderimage_with_target_redhat", "{0} is a required field when target is redhat", true)
+		},
+		func(ut ut.Translator, fe validator.FieldError) string {
+			t, _ := ut.T("required_builderimage_with_target_redhat", "builder image") // fixme ? tag "name" does not work when used at struct level
+
+			return t
+		},
+	)
 
 	V.RegisterTranslation(
 		"logrus",


Test patch:
diff --git a/cmd/cli_test.go b/cmd/cli_test.go
index aee531d..6ddec5a 100644
--- a/cmd/cli_test.go
+++ b/cmd/cli_test.go
@@ -5,22 +5,24 @@ package cmd
 
 import (
 	"bytes"
-	"fmt"
+	"github.com/falcosecurity/driverkit/pkg/driverbuilder/builder"
+	"github.com/falcosecurity/driverkit/pkg/kernelrelease"
 	"io/ioutil"
 	"os"
 	"path/filepath"
 	"runtime"
+	"sort"
 	"strings"
 	"testing"
+	"text/template"
 
 	"github.com/acarl005/stripansi"
 	"gotest.tools/assert"
 )
 
 type expect struct {
-	err            string
-	out            string
-	fmtRuntimeArch bool
+	err string
+	out string
 }
 
 type testCase struct {
@@ -34,32 +36,28 @@ var tests = []testCase{
 	{
 		args: []string{"help"},
 		expect: expect{
-			out:            "testdata/help.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/help.txt",
 		},
 	},
 	{
 		args: []string{"-h"},
 		expect: expect{
-			out:            "testdata/help-flag.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/help-flag.txt",
 		},
 	},
 	{
 		descr: "empty",
 		args:  []string{},
 		expect: expect{
-			out:            "testdata/autohelp.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/autohelp.txt",
 		},
 	},
 	{
 		descr: "invalid/processor",
 		args:  []string{"abc"},
 		expect: expect{
-			out:            "testdata/non-existent-processor.txt",
-			err:            `invalid argument "abc" for "driverkit"`,
-			fmtRuntimeArch: true,
+			out: "testdata/non-existent-processor.txt",
+			err: `invalid argument "abc" for "driverkit"`,
 		},
 	},
 	{
@@ -69,9 +67,8 @@ var tests = []testCase{
 			"wrong",
 		},
 		expect: expect{
-			out:            "testdata/invalid-proxyconfig.txt",
-			err:            "exiting for validation errors",
-			fmtRuntimeArch: true,
+			out: "testdata/invalid-proxyconfig.txt",
+			err: "exiting for validation errors",
 		},
 	},
 	{
@@ -99,9 +96,8 @@ var tests = []testCase{
 		descr: "docker/empty",
 		args:  []string{"docker"},
 		expect: expect{
-			err:            "exiting for validation errors",
-			out:            "testdata/dockernoopts.txt",
-			fmtRuntimeArch: true,
+			err: "exiting for validation errors",
+			out: "testdata/dockernoopts.txt",
 		},
 	},
 	{
@@ -124,8 +120,7 @@ var tests = []testCase{
 			"debug",
 		},
 		expect: expect{
-			out:            "testdata/docker-with-flags-debug.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/docker-with-flags-debug.txt",
 		},
 	},
 	{
@@ -148,8 +143,7 @@ var tests = []testCase{
 			"debug",
 		},
 		expect: expect{
-			out:            "testdata/docker-with-flags-debug.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/docker-with-flags-debug.txt",
 		},
 	},
 	{
@@ -162,24 +156,22 @@ var tests = []testCase{
 			"debug",
 		},
 		expect: expect{
-			out:            "testdata/docker-from-config-debug.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/docker-from-config-debug.txt",
 		},
 	},
 	{
-	    descr: "docker/from-config-file-using-urls",
-        args: []string{
-            "docker",
-            "-c",
-            "testdata/configs/2.yaml",
-            "--loglevel",
-            "debug",
-        },
-        expect: expect{
-            out:            "testdata/docker-override-urls-from-config-debug.txt",
-            fmtRuntimeArch: true,
-        },
-    },
+		descr: "docker/from-config-file-using-urls",
+		args: []string{
+			"docker",
+			"-c",
+			"testdata/configs/2.yaml",
+			"--loglevel",
+			"debug",
+		},
+		expect: expect{
+			out: "testdata/docker-override-urls-from-config-debug.txt",
+		},
+	},
 	{
 		descr: "docker/override-from-config-file",
 		env: map[string]string{
@@ -194,53 +186,50 @@ var tests = []testCase{
 			"debug",
 		},
 		expect: expect{
-			out:            "testdata/docker-override-from-config-debug.txt",
-			fmtRuntimeArch: true,
+			out: "testdata/docker-override-from-config-debug.txt",
+		},
+	},
+	{
+		descr: "docker/build-related-target-azure",
+		args: []string{
+			"docker",
+			"--kernelrelease",
+			"4.15.0-1057-azure",
+			"--kernelversion",
+			"62",
+			"--kernelurls",
+			"http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-azure-headers-4.15.0-1057_4.15.0-1057.62_all.deb",
+			"--kernelurls",
+			"http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-headers-4.15.0-1057-azure_4.15.0-1057.62_amd64.deb",
+			"--target",
+			"ubuntu-aws",
+			"--output-module",
+			"/tmp/falco-ubuntu-azure.ko",
+			"--loglevel",
+			"debug",
+		},
+		expect: expect{
+			out: "testdata/docker-related-target-debug.txt",
 		},
 	},
 	{
-	    descr: "docker/build-related-target-azure",
-	    args: []string{
-	        "docker",
-	        "--kernelrelease",
-	        "4.15.0-1057-azure",
-	        "--kernelversion",
-	        "62",
-	        "--kernelurls",
-	        "http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-azure-headers-4.15.0-1057_4.15.0-1057.62_all.deb",
-	        "--kernelurls",
-	        "http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-headers-4.15.0-1057-azure_4.15.0-1057.62_amd64.deb",
-	        "--target",
-	        "ubuntu-aws",
-	        "--output-module",
-	        "/tmp/falco-ubuntu-azure.ko",
-	        "--loglevel",
-	        "debug",
-        },
-        expect: expect{
-            out:            "testdata/docker-related-target-debug.txt",
-            fmtRuntimeArch: true,
-        },
-    },
-    {
-        descr: "docker/build-target-check-validation-redhat",
-        args: []string{
-            "docker",
-            "--kernelrelease",
-            "4.18.0-348.el8.x86_64",
-            "--target",
-            "redhat",
-            "--output-module",
-            "/tmp/falco-redhat.ko",
-            "--loglevel",
-            "debug",
-        },
-        expect: expect{
-            out:            "testdata/docker-target-redhat-validation-error-debug.txt",
-            err:            "exiting for validation errors",
-            fmtRuntimeArch: true,
-        },
-    },
+		descr: "docker/build-target-check-validation-redhat",
+		args: []string{
+			"docker",
+			"--kernelrelease",
+			"4.18.0-348.el8.x86_64",
+			"--target",
+			"redhat",
+			"--output-module",
+			"/tmp/falco-redhat.ko",
+			"--loglevel",
+			"debug",
+		},
+		expect: expect{
+			out: "testdata/docker-target-redhat-validation-error-debug.txt",
+			err: "exiting for validation errors",
+		},
+	},
 	{
 		descr: "complete/docker/targets",
 		args: []string{
@@ -345,6 +334,81 @@ func run(t *testing.T, test testCase) {
 	}
 }
 
+// Simple explanation:
+// basically we leverage text.template to execute templates for test data.
+// testTemplateData is the actual template structure used to fill the test requested output.
+// It internally stores all the help text, split in section { Desc, Usage, Commands, Flags, Info }
+// plus the flagsTemplateData (that is the template data used to fill Flags fields basically).
+type testTemplateData struct {
+	Desc     string
+	Usage    string
+	Commands string
+	Flags    string
+	Info     string
+	flagsTemplateData
+}
+
+func readTemplateFile(t *testing.T, s string) string {
+	out, err := ioutil.ReadFile("testdata/templates/" + s)
+	assert.NilError(t, err)
+	return string(out)
+}
+
+func initTestTemplateData(t *testing.T, args []string) testTemplateData {
+	td := initFlagsTemplateData(args)
+	return testTemplateData{
+		Usage:             readTemplateFile(t, "usage.txt"),
+		Commands:          readTemplateFile(t, "commands.txt"),
+		Flags:             runTemplate(t, readTemplateFile(t, "flags.txt"), td),
+		Desc:              readTemplateFile(t, "desc.txt"),
+		Info:              readTemplateFile(t, "info.txt"),
+		flagsTemplateData: td,
+	}
+}
+
+type flagsTemplateData struct {
+	Targets             string
+	CurrentArch         string
+	Architectures       string
+	TargetsVerticalList string
+
+	// It is the subcmd being called, ie: driverkit (root) or docker,kubernetes.
+	// It is automatically fetched by args passed to each test case
+	Cmd string
+}
+
+func initFlagsTemplateData(args []string) flagsTemplateData {
+	targets := builder.BuilderByTarget.Targets()
+	sort.Strings(targets)
+
+	cmd := "driverkit"
+	if len(args) > 0 {
+		if args[0] == "docker" {
+			cmd = "docker"
+		} else if args[0] == "kubernetes" {
+			cmd = "kubernetes"
+		}
+	}
+
+	return flagsTemplateData{
+		Targets:             "[" + strings.Join(targets, ",") + "]",
+		CurrentArch:         runtime.GOARCH,
+		Architectures:       kernelrelease.SupportedArchs.String(),
+		TargetsVerticalList: strings.Join(targets, "\n"),
+		Cmd:                 cmd,
+	}
+}
+
+func runTemplate(t *testing.T, f string, td interface{}) string {
+	tplate := template.New("test")
+	parsed, err := tplate.Parse(f)
+	assert.NilError(t, err)
+	buf := bytes.NewBuffer(nil)
+	err = parsed.Execute(buf, td)
+	assert.NilError(t, err)
+	return buf.String()
+}
+
 func TestCLI(t *testing.T) {
 	for _, test := range tests {
 		descr := test.descr
@@ -359,11 +423,8 @@ func TestCLI(t *testing.T) {
 			if err != nil {
 				t.Fatalf("output fixture not found: %v", err)
 			}
-			if !test.expect.fmtRuntimeArch {
-				test.expect.out = string(out)
-			} else {
-				test.expect.out = fmt.Sprintf(string(out), runtime.GOARCH)
-			}
+			td := initTestTemplateData(t, test.args)
+			test.expect.out = runTemplate(t, string(out), td)
 		}
 
 		t.Run(test.descr, func(t *testing.T) {
diff --git a/cmd/testdata/autohelp.txt b/cmd/testdata/autohelp.txt
index 0f0dec1..0dc8e05 100644
--- a/cmd/testdata/autohelp.txt
+++ b/cmd/testdata/autohelp.txt
@@ -1,35 +1,11 @@
 INFO specify a valid processor                     processors="[docker kubernetes]"
-A command line tool to build Falco kernel modules and eBPF probes.
+{{ .Desc }}
 
-Usage:
-  driverkit
-  driverkit [command]
+{{ .Usage }}
 
-Available Commands:
-  completion  Generates completion scripts.
-  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
-  help        Help about any command
-  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+{{ .Commands }}
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for driverkit
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
   -v, --version                   version for driverkit
 
-Use "driverkit [command] --help" for more information about a command.
+{{ .Info }}
diff --git a/cmd/testdata/completion-targets.txt b/cmd/testdata/completion-targets.txt
index 88d98ae..7c5e1eb 100644
--- a/cmd/testdata/completion-targets.txt
+++ b/cmd/testdata/completion-targets.txt
@@ -1,17 +1,3 @@
-amazonlinux
-amazonlinux2
-amazonlinux2022
-archlinux
-centos
-debian
-flatcar
-minikube
-photon
-redhat
-rocky
-ubuntu
-ubuntu-aws
-ubuntu-generic
-vanilla
+{{ .TargetsVerticalList }}
 :0
 Completion ended with directive: ShellCompDirectiveDefault
diff --git a/cmd/testdata/docker-from-config-debug.txt b/cmd/testdata/docker-from-config-debug.txt
index bf707c3..14c4450 100644
--- a/cmd/testdata/docker-from-config-debug.txt
+++ b/cmd/testdata/docker-from-config-debug.txt
@@ -1,3 +1,3 @@
 INFO using config file                             file=testdata/configs/1.yaml
-DEBU running with options                          arch=%s driverversion=master kernelrelease=4.15.0-1057-aws kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
+DEBU running with options                          arch={{ .CurrentArch }} driverversion=master kernelrelease=4.15.0-1057-aws kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
 INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-override-from-config-debug.txt b/cmd/testdata/docker-override-from-config-debug.txt
index 5ee5b39..1845cbe 100644
--- a/cmd/testdata/docker-override-from-config-debug.txt
+++ b/cmd/testdata/docker-override-from-config-debug.txt
@@ -1,3 +1,3 @@
 INFO using config file                             file=testdata/configs/1.yaml
-DEBU running with options                          arch=%s driverversion=master kernelrelease=4.15.0-1057-aws kernelversion=229 output-module=/tmp/override.ko target=ubuntu-aws
+DEBU running with options                          arch={{ .CurrentArch }} driverversion=master kernelrelease=4.15.0-1057-aws kernelversion=229 output-module=/tmp/override.ko target=ubuntu-aws
 INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-override-urls-from-config-debug.txt b/cmd/testdata/docker-override-urls-from-config-debug.txt
index c035e86..4055aec 100644
--- a/cmd/testdata/docker-override-urls-from-config-debug.txt
+++ b/cmd/testdata/docker-override-urls-from-config-debug.txt
@@ -1,3 +1,3 @@
 INFO using config file                             file=testdata/configs/2.yaml
-DEBU running with options                          arch=%s driverversion=master kernelrelease=4.15.0-1057-aws kernelurls="[https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-aws-headers-4.15.0-1057_4.15.0-1057.59_all.deb https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-headers-4.15.0-1057-aws_4.15.0-1057.59_amd64.deb]" kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
+DEBU running with options                          arch={{ .CurrentArch }} driverversion=master kernelrelease=4.15.0-1057-aws kernelurls="[https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-aws-headers-4.15.0-1057_4.15.0-1057.59_all.deb https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-headers-4.15.0-1057-aws_4.15.0-1057.59_amd64.deb]" kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
 INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-related-target-debug.txt b/cmd/testdata/docker-related-target-debug.txt
index 091c9aa..dbcacc5 100644
--- a/cmd/testdata/docker-related-target-debug.txt
+++ b/cmd/testdata/docker-related-target-debug.txt
@@ -1,3 +1,3 @@
 DEBU running without a configuration file         
-DEBU running with options                          arch=%s driverversion=master kernelrelease=4.15.0-1057-azure kernelurls="[http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-azure-headers-4.15.0-1057_4.15.0-1057.62_all.deb http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-headers-4.15.0-1057-azure_4.15.0-1057.62_amd64.deb]" kernelversion=62 output-module=/tmp/falco-ubuntu-azure.ko target=ubuntu-aws
+DEBU running with options                          arch={{ .CurrentArch }} driverversion=master kernelrelease=4.15.0-1057-azure kernelurls="[http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-azure-headers-4.15.0-1057_4.15.0-1057.62_all.deb http://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-azure/linux-headers-4.15.0-1057-azure_4.15.0-1057.62_amd64.deb]" kernelversion=62 output-module=/tmp/falco-ubuntu-azure.ko target=ubuntu-aws
 INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/docker-target-redhat-validation-error-debug.txt b/cmd/testdata/docker-target-redhat-validation-error-debug.txt
index 7acfc5f..4439a94 100644
--- a/cmd/testdata/docker-target-redhat-validation-error-debug.txt
+++ b/cmd/testdata/docker-target-redhat-validation-error-debug.txt
@@ -4,23 +4,5 @@ Error: exiting for validation errors
 Usage:
   driverkit docker [flags]
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for docker
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
 
diff --git a/cmd/testdata/docker-with-flags-debug.txt b/cmd/testdata/docker-with-flags-debug.txt
index 3091121..9b34047 100644
--- a/cmd/testdata/docker-with-flags-debug.txt
+++ b/cmd/testdata/docker-with-flags-debug.txt
@@ -1,3 +1,3 @@
 DEBU running without a configuration file         
-DEBU running with options                          arch=%s driverversion=master kernelrelease=4.15.0-1057-aws kernelurls="[https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-aws-headers-4.15.0-1057_4.15.0-1057.59_all.deb https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-headers-4.15.0-1057-aws_4.15.0-1057.59_amd64.deb]" kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
+DEBU running with options                          arch={{ .CurrentArch }} driverversion=master kernelrelease=4.15.0-1057-aws kernelurls="[https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-aws-headers-4.15.0-1057_4.15.0-1057.59_all.deb https://mirrors.edge.kernel.org/ubuntu/pool/main/l/linux-aws/linux-headers-4.15.0-1057-aws_4.15.0-1057.59_amd64.deb]" kernelversion=59 output-module=/tmp/falco-ubuntu-aws.ko target=ubuntu-aws
 INFO driver building, it will take a few seconds   processor=docker
diff --git a/cmd/testdata/dockernoopts.txt b/cmd/testdata/dockernoopts.txt
index f675e14..82b705b 100644
--- a/cmd/testdata/dockernoopts.txt
+++ b/cmd/testdata/dockernoopts.txt
@@ -6,23 +6,5 @@ Error: exiting for validation errors
 Usage:
   driverkit docker [flags]
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for docker
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
 
diff --git a/cmd/testdata/help-flag.txt b/cmd/testdata/help-flag.txt
index 031baea..2f3243c 100644
--- a/cmd/testdata/help-flag.txt
+++ b/cmd/testdata/help-flag.txt
@@ -1,34 +1,10 @@
-A command line tool to build Falco kernel modules and eBPF probes.
+{{ .Desc }}
 
-Usage:
-  driverkit
-  driverkit [command]
+{{ .Usage }}
 
-Available Commands:
-  completion  Generates completion scripts.
-  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
-  help        Help about any command
-  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+{{ .Commands }}
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for driverkit
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
   -v, --version                   version for driverkit
 
-Use "driverkit [command] --help" for more information about a command.
+{{ .Info }}
diff --git a/cmd/testdata/help.txt b/cmd/testdata/help.txt
index ad1938e..80b0eee 100644
--- a/cmd/testdata/help.txt
+++ b/cmd/testdata/help.txt
@@ -1,33 +1,9 @@
-A command line tool to build Falco kernel modules and eBPF probes.
+{{ .Desc }}
 
-Usage:
-  driverkit
-  driverkit [command]
+{{ .Usage }}
 
-Available Commands:
-  completion  Generates completion scripts.
-  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
-  help        Help about any command
-  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+{{ .Commands }}
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for driverkit
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
 
-Use "driverkit [command] --help" for more information about a command.
+{{ .Info }}
diff --git a/cmd/testdata/invalid-proxyconfig.txt b/cmd/testdata/invalid-proxyconfig.txt
index 772144d..ce0356a 100644
--- a/cmd/testdata/invalid-proxyconfig.txt
+++ b/cmd/testdata/invalid-proxyconfig.txt
@@ -1,35 +1,11 @@
 ERRO error validating config options               error="proxy url must start with http:// or https:// or socks5:// prefix"
 Error: exiting for validation errors
-Usage:
-  driverkit
-  driverkit [command]
+{{ .Usage }}
 
-Available Commands:
-  completion  Generates completion scripts.
-  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
-  help        Help about any command
-  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+{{ .Commands }}
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for driverkit
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
   -v, --version                   version for driverkit
 
-Use "driverkit [command] --help" for more information about a command.
+{{ .Info }}
 
diff --git a/cmd/testdata/non-existent-processor.txt b/cmd/testdata/non-existent-processor.txt
index 917c270..bcf7bae 100644
--- a/cmd/testdata/non-existent-processor.txt
+++ b/cmd/testdata/non-existent-processor.txt
@@ -1,34 +1,10 @@
 Error: invalid argument "abc" for "driverkit"
-Usage:
-  driverkit
-  driverkit [command]
+{{ .Usage }}
 
-Available Commands:
-  completion  Generates completion scripts.
-  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
-  help        Help about any command
-  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
+{{ .Commands }}
 
-Flags:
-      --architecture string       target architecture for the built driver (default "%s")
-      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
-  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
-      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
-      --dryrun                    do not actually perform the action
-  -h, --help                      help for driverkit
-      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
-      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
-      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
-      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
-  -l, --loglevel string           log level (default "info")
-      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
-      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
-      --output-module string      filepath where to save the resulting kernel module
-      --output-probe string       filepath where to save the resulting eBPF probe
-      --proxy string              the proxy to use to download data
-  -t, --target string             the system to target the build for
-      --timeout int               timeout in seconds (default 120)
+{{ .Flags }}
   -v, --version                   version for driverkit
 
-Use "driverkit [command] --help" for more information about a command.
+{{ .Info }}
 
diff --git a/cmd/testdata/templates/commands.txt b/cmd/testdata/templates/commands.txt
new file mode 100644
index 0000000..7889e23
--- /dev/null
+++ b/cmd/testdata/templates/commands.txt
@@ -0,0 +1,5 @@
+Available Commands:
+  completion  Generates completion scripts.
+  docker      Build Falco kernel modules and eBPF probes against a docker daemon.
+  help        Help about any command
+  kubernetes  Build Falco kernel modules and eBPF probes against a Kubernetes cluster.
\ No newline at end of file
diff --git a/cmd/testdata/templates/desc.txt b/cmd/testdata/templates/desc.txt
new file mode 100644
index 0000000..c47961b
--- /dev/null
+++ b/cmd/testdata/templates/desc.txt
@@ -0,0 +1,1 @@
+A command line tool to build Falco kernel modules and eBPF probes.
\ No newline at end of file
diff --git a/cmd/testdata/templates/flags.txt b/cmd/testdata/templates/flags.txt
new file mode 100644
index 0000000..41953c4
--- /dev/null
+++ b/cmd/testdata/templates/flags.txt
@@ -0,0 +1,19 @@
+Flags:
+      --architecture string       target architecture for the built driver, one of {{ .Architectures }} (default "{{ .CurrentArch }}")
+      --builderimage string       docker image to be used to build the kernel module and eBPF probe. If not provided, the default image will be used. (default "falcosecurity/driverkit-builder:latest")
+  -c, --config string             config file path (default $HOME/.driverkit.yaml if exists)
+      --driverversion string      driver version as a git commit hash or as a git tag (default "master")
+      --dryrun                    do not actually perform the action
+  -h, --help                      help for {{ .Cmd }}
+      --kernelconfigdata string   base64 encoded kernel config data: in some systems it can be found under the /boot directory, in other it is gzip compressed under /proc
+      --kernelrelease string      kernel release to build the module for, it can be found by executing 'uname -v'
+      --kernelurls strings        list of kernel header urls (e.g. --kernelurls <URL1> --kernelurls <URL2> --kernelurls "<URL3>,<URL4>")
+      --kernelversion string      kernel version to build the module for, it's the numeric value after the hash when you execute 'uname -v' (default "1")
+  -l, --loglevel string           log level (default "info")
+      --moduledevicename string   kernel module device name (the default is falco, so the device will be under /dev/falco*) (default "falco")
+      --moduledrivername string   kernel module driver name, i.e. the name you see when you check installed modules via lsmod (default "falco")
+      --output-module string      filepath where to save the resulting kernel module
+      --output-probe string       filepath where to save the resulting eBPF probe
+      --proxy string              the proxy to use to download data
+  -t, --target string             the system to target the build for, one of {{ .Targets }}
+      --timeout int               timeout in seconds (default 120)
\ No newline at end of file
diff --git a/cmd/testdata/templates/info.txt b/cmd/testdata/templates/info.txt
new file mode 100644
index 0000000..3161309
--- /dev/null
+++ b/cmd/testdata/templates/info.txt
@@ -0,0 +1,1 @@
+Use "driverkit [command] --help" for more information about a command.
\ No newline at end of file
diff --git a/cmd/testdata/templates/usage.txt b/cmd/testdata/templates/usage.txt
new file mode 100644
index 0000000..75f0e4b
--- /dev/null
+++ b/cmd/testdata/templates/usage.txt
@@ -0,0 +1,3 @@
+Usage:
+  driverkit
+  driverkit [command]
\ No newline at end of file