From 89c83d34c8dbc2b28dce4d80af895048151815b9 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 13:13:34 +0200 Subject: [PATCH 01/14] Makefile: fix target dependency order Fix Ansible warnings by ensuring proper build order: extra_vars.yaml -> ansible.cfg -> hosts -> nodes -> rest This fixes Ansible WARNINGS introduced by d7028bde ("terraform: remove redundant ansible inventory and connection overrides") while executing terraform Makefile targets. Reported by Chuck Lever. Warnings: ==> [terraform/aws/terraform.tfvars] + ansible-playbook playbooks/gen_tfvars.yml --extra-vars=@./extra_vars.yaml [WARNING]: No inventory was parsed, only implicit localhost is available [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all' Generated-by: Claude AI Reported-by: Chuck Lever Reviewed-by: Chuck Lever Link: https://lore.kernel.org/r/20250922-makefile-targets-order-v1-1-d4adb19ffa88@samsung.com Signed-off-by: Daniel Gomez --- Makefile | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/Makefile b/Makefile index dbd52ca3d..fb7e9b816 100644 --- a/Makefile +++ b/Makefile @@ -124,6 +124,23 @@ LOCALHOST_SETUP_WORK := ANSIBLE_EXTRA_ARGS += $(LOCAL_DEVELOPMENT_ARGS) +# We may not need the extra_args.yaml file all the time. If this file is empty +# you don't need it. All of our ansible kdevops roles check for this file +# without you having to specify it as an extra_args=@extra_args.yaml file. This +# helps us with allowing users call ansible on the command line themselves, +# instead of using the make constructs we have built here. +# Core dependencies now added before provision.Makefile include +ifneq (,$(ANSIBLE_EXTRA_ARGS)) +DEFAULT_DEPS += $(KDEVOPS_EXTRA_VARS) +endif + +DEFAULT_DEPS += $(ANSIBLE_CFG_FILE) +DEFAULT_DEPS += $(ANSIBLE_INVENTORY_FILE) + +ifneq (,$(KDEVOPS_NODES)) +DEFAULT_DEPS += $(KDEVOPS_NODES) +endif + include scripts/provision.Makefile include scripts/firstconfig.Makefile include scripts/systemd-timesync.Makefile @@ -152,15 +169,6 @@ ifeq (y,$(CONFIG_WORKFLOW_KOTD_ENABLE)) include scripts/kotd.Makefile endif # WORKFLOW_KOTD_ENABLE -# We may not need the extra_args.yaml file all the time. If this file is empty -# you don't need it. All of our ansible kdevops roles check for this file -# without you having to specify it as an extra_args=@extra_args.yaml file. This -# helps us with allowing users call ansible on the command line themselves, -# instead of using the make constructs we have built here. -ifneq (,$(ANSIBLE_EXTRA_ARGS)) -DEFAULT_DEPS += $(KDEVOPS_EXTRA_VARS) -endif - DEFAULT_DEPS += $(DEFAULT_DEPS_REQS_EXTRA_VARS) include scripts/install-menuconfig-deps.Makefile @@ -250,14 +258,12 @@ ifneq (,$(KDEVOPS_BRING_UP_DEPS)) include scripts/bringup.Makefile endif -DEFAULT_DEPS += $(ANSIBLE_INVENTORY_FILE) -$(ANSIBLE_INVENTORY_FILE): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_HOSTS_TEMPLATE) $(KDEVOPS_NODES) $(KDEVOPS_EXTRA_VARS) +$(ANSIBLE_INVENTORY_FILE): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_HOSTS_TEMPLATE) $(KDEVOPS_EXTRA_VARS) $(Q)ANSIBLE_LOCALHOST_WARNING=False ANSIBLE_INVENTORY_UNPARSED_WARNING=False \ ansible-playbook $(ANSIBLE_VERBOSE) \ $(KDEVOPS_PLAYBOOKS_DIR)/gen_hosts.yml \ --extra-vars=@./extra_vars.yaml -DEFAULT_DEPS += $(KDEVOPS_NODES) $(KDEVOPS_NODES): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_NODES_TEMPLATE) $(KDEVOPS_EXTRA_VARS) $(Q)ANSIBLE_LOCALHOST_WARNING=False ANSIBLE_INVENTORY_UNPARSED_WARNING=False \ ansible-playbook $(ANSIBLE_VERBOSE) --connection=local \ From db4e6adacd8bc5438e8d82e9b3427d77dbe3b400 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 13:13:35 +0200 Subject: [PATCH 02/14] gen_hosts: add localhost to generic workflow template Add localhost ansible_connection=local to the generic workflow template to match other workflow templates updated in commit 1cf0800c ("gen_hosts: templates: include localhost in the all group"). This ensures all inventory templates include localhost with proper connection settings, allowing simplified ansible-playbook calls without explicit connection flags. Fixes: f4a4e347 ("gen_hosts: Add missing generic workflow template") Generated-by: Claude AI Reviewed-by: Chuck Lever Link: https://lore.kernel.org/r/20250922-makefile-targets-order-v1-2-d4adb19ffa88@samsung.com Signed-off-by: Daniel Gomez --- playbooks/roles/gen_hosts/templates/workflows/generic.j2 | 1 + 1 file changed, 1 insertion(+) diff --git a/playbooks/roles/gen_hosts/templates/workflows/generic.j2 b/playbooks/roles/gen_hosts/templates/workflows/generic.j2 index c18ec9fcd..20b2f3896 100644 --- a/playbooks/roles/gen_hosts/templates/workflows/generic.j2 +++ b/playbooks/roles/gen_hosts/templates/workflows/generic.j2 @@ -6,6 +6,7 @@ {% set nodes = [kdevops_host_prefix] %} {% endif %} [all] +localhost ansible_connection=local {% for node in nodes %} {{ node }} {% endfor %} From 6ec1325c3497e7fd020f4be3a0641d91a1c272c9 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 13:13:36 +0200 Subject: [PATCH 03/14] ansible_cfg: fix Python interpreter discovery warning Add ansible_python_interpreter setting to suppress the Python interpreter discovery warning during ansible.cfg generation. Uses ansible_playbook_python variable to ensure consistency with the Python interpreter running the playbook. Generated-by: Claude AI Reviewed-by: Chuck Lever Link: https://lore.kernel.org/r/20250922-makefile-targets-order-v1-3-d4adb19ffa88@samsung.com Signed-off-by: Daniel Gomez --- playbooks/ansible_cfg.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/playbooks/ansible_cfg.yml b/playbooks/ansible_cfg.yml index 872f2ee48..a6c802273 100644 --- a/playbooks/ansible_cfg.yml +++ b/playbooks/ansible_cfg.yml @@ -1,5 +1,7 @@ --- - name: Ansible Configuration Role hosts: localhost + vars: + ansible_python_interpreter: "{{ ansible_playbook_python }}" roles: - role: ansible_cfg From 1f36f1d183cc52f706d87938719e9925572dae3d Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 13:13:37 +0200 Subject: [PATCH 04/14] Makefile: remove warnings from ANSIBLE_CFG_FILE target Remove ANSIBLE_LOCALHOST_WARNING and ANSIBLE_INVENTORY_UNPARSED_WARNING from the ansible.cfg generation target since it uses explicit --inventory localhost, and doesn't need the hosts file to exist yet. Generated-by: Claude AI Reviewed-by: Chuck Lever Link: https://lore.kernel.org/r/20250922-makefile-targets-order-v1-4-d4adb19ffa88@samsung.com Signed-off-by: Daniel Gomez --- Makefile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Makefile b/Makefile index fb7e9b816..5d855b6ec 100644 --- a/Makefile +++ b/Makefile @@ -228,8 +228,7 @@ include scripts/gen-nodes.Makefile false) $(ANSIBLE_CFG_FILE): .config - $(Q)ANSIBLE_LOCALHOST_WARNING=False ANSIBLE_INVENTORY_UNPARSED_WARNING=False \ - ansible-playbook $(ANSIBLE_VERBOSE) --connection=local \ + $(Q)ansible-playbook $(ANSIBLE_VERBOSE) --connection=local \ --inventory localhost, \ $(KDEVOPS_PLAYBOOKS_DIR)/ansible_cfg.yml \ --extra-vars=@./.extra_vars_auto.yaml From 0383c1d5bab13ad93b60d9ada2ae03ba61c07e49 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 13:13:38 +0200 Subject: [PATCH 05/14] Makefile: simplify KDEVOPS_NODES ansible-playbook call Remove connection flags and warning suppressions from the KDEVOPS_NODES target since all inventory templates now include localhost with proper ansible_connection=local settings. This allows the playbook to use the generated ansible.cfg and hosts files automatically without explicit overrides. Generated-by: Claude AI Reviewed-by: Chuck Lever Link: https://lore.kernel.org/r/20250922-makefile-targets-order-v1-5-d4adb19ffa88@samsung.com Signed-off-by: Daniel Gomez --- Makefile | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 5d855b6ec..edf62db69 100644 --- a/Makefile +++ b/Makefile @@ -264,9 +264,7 @@ $(ANSIBLE_INVENTORY_FILE): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_HOSTS_TEMPLATE) --extra-vars=@./extra_vars.yaml $(KDEVOPS_NODES): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_NODES_TEMPLATE) $(KDEVOPS_EXTRA_VARS) - $(Q)ANSIBLE_LOCALHOST_WARNING=False ANSIBLE_INVENTORY_UNPARSED_WARNING=False \ - ansible-playbook $(ANSIBLE_VERBOSE) --connection=local \ - --inventory localhost, \ + $(Q)ansible-playbook $(ANSIBLE_VERBOSE) \ $(KDEVOPS_PLAYBOOKS_DIR)/gen_nodes.yml \ --extra-vars=@./extra_vars.yaml From 48659c27c62f1d2ffff9d455d41183c07bf0efdb Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 13:13:39 +0200 Subject: [PATCH 06/14] Makefile: add explicit connection for inventory generation Add --connection=local and --inventory localhost, to the inventory generation target since we're generating the hosts file and don't have a proper inventory available yet. This avoids inventory parsing warnings by being explicit about the execution context, following the same pattern as ansible.cfg generation. Generated-by: Claude AI Reviewed-by: Chuck Lever Link: https://lore.kernel.org/r/20250922-makefile-targets-order-v1-6-d4adb19ffa88@samsung.com Signed-off-by: Daniel Gomez --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index edf62db69..90e8091dc 100644 --- a/Makefile +++ b/Makefile @@ -258,8 +258,8 @@ include scripts/bringup.Makefile endif $(ANSIBLE_INVENTORY_FILE): .config $(ANSIBLE_CFG_FILE) $(KDEVOPS_HOSTS_TEMPLATE) $(KDEVOPS_EXTRA_VARS) - $(Q)ANSIBLE_LOCALHOST_WARNING=False ANSIBLE_INVENTORY_UNPARSED_WARNING=False \ - ansible-playbook $(ANSIBLE_VERBOSE) \ + $(Q)ansible-playbook $(ANSIBLE_VERBOSE) --connection=local \ + --inventory localhost, \ $(KDEVOPS_PLAYBOOKS_DIR)/gen_hosts.yml \ --extra-vars=@./extra_vars.yaml From d2c30bdd3f2caa591edeeb4b5be3f5401101d892 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 08:17:05 +0000 Subject: [PATCH 07/14] base_image: optimize VM image copying with reflinks and fix same-file handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace ansible.builtin.copy with cp --reflink=auto for significant performance improvement on XFS filesystems with reflink support: - 20GB image copy time: ~54s → ~1s (when reflinks are supported) - Automatic fallback to regular copy on non-reflink filesystems - Use direct cp command for better control over reflink behavior Add condition to prevent copying when source and destination are the same file. This was not needed with ansible.builtin.copy (which handles this case automatically) but is required with direct cp command which fails with "files are the same" error. The condition `custom_image != base_image_pathname` ensures copy only occurs when paths differ, maintaining compatibility with configurations where both variables point to the same location. Generated-by: Claude AI Signed-off-by: Daniel Gomez --- .../roles/base_image/tasks/custom-image.yml | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/playbooks/roles/base_image/tasks/custom-image.yml b/playbooks/roles/base_image/tasks/custom-image.yml index bcf35933d..bac834e83 100644 --- a/playbooks/roles/base_image/tasks/custom-image.yml +++ b/playbooks/roles/base_image/tasks/custom-image.yml @@ -338,13 +338,21 @@ - "{{ custom_image_dir }}" changed_when: true -- name: Copy custom image to base image location +- name: Copy custom image to base image location (with automatic reflink optimization) become: true become_method: ansible.builtin.sudo - ansible.builtin.copy: - src: "{{ custom_image }}" - dest: "{{ base_image_pathname }}" - remote_src: true + ansible.builtin.command: + cmd: "cp --reflink=auto '{{ custom_image }}' '{{ base_image_pathname }}'" + when: + - custom_image_stat.stat.exists or custom_image_download is changed + - custom_image != base_image_pathname + +- name: Set proper permissions on base image + become: true + become_method: ansible.builtin.sudo + ansible.builtin.file: + path: "{{ base_image_pathname }}" mode: "u=rw,g=r,o=r" when: - custom_image_stat.stat.exists or custom_image_download is changed + - custom_image != base_image_pathname From f00efe7a2d7c11eed0c84a5f5aa4ab722e6c1636 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 14:32:44 +0200 Subject: [PATCH 08/14] defconfig: add ci fragment Introduce a CI-specific fragment to enable the configuration options required for continuous integration. Signed-off-by: Daniel Gomez --- defconfigs/configs/ci.config | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 defconfigs/configs/ci.config diff --git a/defconfigs/configs/ci.config b/defconfigs/configs/ci.config new file mode 100644 index 000000000..00af941c2 --- /dev/null +++ b/defconfigs/configs/ci.config @@ -0,0 +1,7 @@ +CONFIG_ANSIBLE_CFG_CALLBACK_PLUGIN_PROFILE_TASKS=y +CONFIG_ANSIBLE_CFG_FORKS=30 +CONFIG_BOOTLINUX_CCACHE_SYSTEM_WIDE=y +CONFIG_BOOTLINUX_CCACHE=y +CONFIG_BOOTLINUX_CLEAN_BEFORE_BUILD=y +CONFIG_BOOTLINUX_REPRODUCIBLE_BUILDS=y +CONFIG_KDEVOPS_MAKE_VERBOSE=y From 9fc69540a9e4a00913c8d4765c973fd288e36dc6 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Mon, 22 Sep 2025 11:54:14 +0000 Subject: [PATCH 09/14] kconfig: add KDEVOPS_MAKE_VERBOSE option for persistent verbose output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a new kconfig option in the "Kdevops configuration" menu to enable verbose make output (V=1) by default. This promotes make verbosity from a command-line-only option to a first-class configuration setting. Features: - KDEVOPS_MAKE_VERBOSE bool option in menuconfig - Early parsing in main Makefile before V= logic - Command line override support with proper priority: 1. CLI V=0/V=1 (highest priority) 2. CONFIG_KDEVOPS_MAKE_VERBOSE=y (persistent default) 3. Built-in quiet mode (fallback) Usage examples: - make target → Uses kconfig setting - make V=0 target → CLI overrides to quiet - make V=1 target → CLI overrides to verbose This allows users to set a persistent verbosity preference while maintaining full command-line flexibility, following industry standard patterns used by Rust/Cargo, Kubernetes, and other tools. Generated-by: Claude AI Signed-off-by: dagomez --- Makefile | 7 +++++++ kconfigs/Kconfig.kdevops | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Makefile b/Makefile index 90e8091dc..9ea479c95 100644 --- a/Makefile +++ b/Makefile @@ -64,6 +64,13 @@ define print_target echo "==> [$1]" endef +# Parse kconfig verbosity setting early, allow CLI to override +ifneq (,$(wildcard .config)) +ifeq ($(CONFIG_KDEVOPS_MAKE_VERBOSE),y) +V ?= 1 +endif +endif + ifeq ($(V),1) export Q=@$(call print_target,$@) && set -x && export NQ=true diff --git a/kconfigs/Kconfig.kdevops b/kconfigs/Kconfig.kdevops index c2362adf3..d22edd012 100644 --- a/kconfigs/Kconfig.kdevops +++ b/kconfigs/Kconfig.kdevops @@ -76,6 +76,24 @@ config KDEVOPS_CUSTOM_SSH_KEXALGORITHMS Some distributions, such as older distributions, may require a custom ssh configuration entry for the KexAlgorithms parameter. +config KDEVOPS_MAKE_VERBOSE + bool "Enable verbose make output (V=1)" + default n + help + Enable verbose output for all make operations. This is equivalent + to running 'make V=1' and shows all executed commands for debugging. + + When enabled, you'll see: + - All shell commands being executed + - Full ansible-playbook command lines + - Detailed build output + + This is useful for debugging build issues and understanding what + kdevops is doing behind the scenes. You can still override this + setting on the command line with 'make V=0' or 'make V=1'. + + If unsure, say N. + config KDEVOPS_BASELINE_AND_DEV bool "Enable both a baseline and development system per target test" default n From 5bd2bb937079b092ff48e60fbd5559f4b42c69db Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 10:29:10 +0000 Subject: [PATCH 10/14] ansible: enhance DIY callback output format for improved readability MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Improve the DIY callback output format to provide cleaner, more scannable task execution display: - Show target hosts in task start messages using ansible_play_hosts - Use visual status indicators: ✓ for success, ⊘ for skipped tasks - Remove redundant task name repetition in completion messages - Add indentation to status messages for better visual hierarchy - Remove unnecessary "start:" messages to reduce noise - Keep (changed) and FAILED messages with full context Output format: TASK: Task name [target,hosts] ✓ [actual_host] ⊘ [skipped_host] This creates clean, scannable output similar to systemd service status with clear visual indicators while showing host targeting information upfront and execution results with proper visual hierarchy. Generated-by: Claude AI Signed-off-by: Daniel Gomez --- playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 b/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 index f3cc1797c..c8981e098 100644 --- a/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 +++ b/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 @@ -17,17 +17,17 @@ inventory = {{ ansible_cfg_inventory }} [callback_diy] playbook_on_play_start_msg = PLAY: {{ '{{' }} ansible_callback_diy.play.name | default('') | upper {{ '}}' }} playbook_on_play_start_msg_color = bright blue -playbook_on_task_start_msg = TASK: {{ '{{' }} ansible_callback_diy.task.name | default('') {{ '}}' }} +playbook_on_task_start_msg = TASK: {{ '{{' }} ansible_callback_diy.task.name | default('') {{ '}}' }} [{{ '{{' }} ansible_play_hosts | join(',') {{ '}}' }}] playbook_on_task_start_msg_color = yellow -runner_on_start_msg = start: [{{ '{{' }} ansible_callback_diy.host.name | default('host') {{ '}}' }}] +runner_on_start_msg = runner_on_start_msg_color = yellow -runner_on_ok_msg = ok: [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] +runner_on_ok_msg = ⠀⠀✓ [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] runner_on_ok_msg_color = green -runner_on_changed_msg = changed: [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] +runner_on_changed_msg = ⠀⠀(changed) [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] runner_on_changed_msg_color = bright yellow -runner_on_failed_msg = FAILED: [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] => {{ '{{' }} msg | default('') {{ '}}' }}{{ '{%' }} if stderr is defined and stderr | length > 0 {{ '%}' }} STDERR: {{ '{{' }} stderr {{ '}}' }}{{ '{%' }} endif {{ '%}' }}{{ '{%' }} if stdout is defined and stdout | length > 0 {{ '%}' }} STDOUT: {{ '{{' }} stdout {{ '}}' }}{{ '{%' }} endif {{ '%}' }} +runner_on_failed_msg = ⠀⠀FAILED: [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] => {{ '{{' }} msg | default('') {{ '}}' }}{{ '{%' }} if stderr is defined and stderr | length > 0 {{ '%}' }} STDERR: {{ '{{' }} stderr {{ '}}' }}{{ '{%' }} endif {{ '%}' }}{{ '{%' }} if stdout is defined and stdout | length > 0 {{ '%}' }} STDOUT: {{ '{{' }} stdout {{ '}}' }}{{ '{%' }} endif {{ '%}' }} runner_on_failed_msg_color = bright red -runner_on_skipped_msg = skipped: [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] +runner_on_skipped_msg = ⠀⠀⊘ [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] runner_on_skipped_msg_color = cyan runner_on_unreachable_msg = UNREACHABLE: [{{ '{{' }} ansible_callback_diy.result.host | default('host') {{ '}}' }}] runner_on_unreachable_msg_color = bright magenta From c14181addc9037b0f0dc463b379ea2a7a9daaa5d Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 09:41:00 +0000 Subject: [PATCH 11/14] ansible: add profile_tasks callback timing analysis support Add new Kconfig option ANSIBLE_CFG_CALLBACK_PLUGIN_PROFILE_TASKS to enable the ansible.posix.profile_tasks callback plugin. This provides timing analysis similar to systemd-analyze blame, showing task execution times and generating a TASKS RECAP section with the slowest tasks at the end of playbook execution. The option is disabled by default to maintain clean output for existing workflows, but can be enabled when performance analysis is needed. Generated-by: Claude AI Signed-off-by: Daniel Gomez --- kconfigs/Kconfig.ansible_cfg | 14 ++++++++++++++ .../roles/ansible_cfg/templates/ansible.cfg.j2 | 8 ++++++++ 2 files changed, 22 insertions(+) diff --git a/kconfigs/Kconfig.ansible_cfg b/kconfigs/Kconfig.ansible_cfg index 430c0e4c7..c04532e81 100644 --- a/kconfigs/Kconfig.ansible_cfg +++ b/kconfigs/Kconfig.ansible_cfg @@ -156,6 +156,20 @@ config ANSIBLE_CFG_CALLBACK_PLUGIN_SHOW_TASK_PATH_ON_FAILURE the line number. This information is displayed automatically for every task when running with -vv or greater verbosity. https://docs.ansible.com/ansible/latest/collections/community/general/dense_callback.html#parameter-show_task_path_on_failure + +config ANSIBLE_CFG_CALLBACK_PLUGIN_PROFILE_TASKS + bool "Enable profile_tasks callback for timing analysis" + output yaml + default n + help + Enable the profile_tasks callback plugin to provide timing analysis + similar to systemd-analyze blame. This shows how long each task takes + to execute and provides a summary of the slowest tasks at the end. + + When enabled, this adds timing information to playbook execution + and generates a "TASKS RECAP" section showing execution times. + + https://docs.ansible.com/ansible/latest/collections/ansible/posix/profile_tasks_callback.html endmenu config ANSIBLE_CFG_DEPRECATION_WARNINGS diff --git a/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 b/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 index c8981e098..f3f6c723f 100644 --- a/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 +++ b/playbooks/roles/ansible_cfg/templates/ansible.cfg.j2 @@ -1,4 +1,7 @@ [defaults] +{% if ansible_cfg_callback_plugin_profile_tasks %} +callbacks_enabled = ansible.posix.profile_tasks, {{ ansible_cfg_callback_plugin_string }} +{% endif %} deprecation_warnings = {{ ansible_cfg_deprecation_warnings }} stdout_callback = {{ ansible_cfg_callback_plugin_string }} check_mode_markers = {{ ansible_cfg_callback_plugin_check_mode_markers }} @@ -39,6 +42,11 @@ playbook_on_stats_msg = PLAYBOOK SUMMARY {{ '{%' }} endfor {{ '%}' }} playbook_on_stats_msg_color = bright green {% endif %} +{% if ansible_cfg_callback_plugin_profile_tasks %} + +[callback_profile_tasks] +summary_only = true +{% endif %} {% if ansible_facts['distribution'] == 'openSUSE' %} [connection] retries = {{ ansible_cfg_reconnection_retries }} From 2e3a2fd53779635c7e3a55cfbe5504424c01090a Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 14:30:06 +0200 Subject: [PATCH 12/14] github: refactor Signed-off-by: Daniel Gomez --- .github/actions/archive/action.yml | 28 +++ .github/actions/cleanup/action.yml | 24 +++ .github/actions/setup/action.yml | 187 ++++++++++++++++++ .github/actions/test/action.yml | 213 +++++++++++++++++++++ .github/workflows/destroy.yml | 69 +++++++ .github/workflows/fstests.yml | 98 ---------- .github/workflows/main.yml | 97 ++++++++++ .github/workflows/manual.yml | 133 +++++++++++++ .github/workflows/schedule.yml | 53 ++++++ scripts/generate_ci_commit_message.sh | 263 ++++++++++++++++++++++++++ scripts/github_output.sh | 9 + scripts/korg-releases.py | 94 +++++++++ 12 files changed, 1170 insertions(+), 98 deletions(-) create mode 100644 .github/actions/archive/action.yml create mode 100644 .github/actions/cleanup/action.yml create mode 100644 .github/actions/setup/action.yml create mode 100644 .github/actions/test/action.yml create mode 100644 .github/workflows/destroy.yml delete mode 100644 .github/workflows/fstests.yml create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/manual.yml create mode 100644 .github/workflows/schedule.yml create mode 100755 scripts/generate_ci_commit_message.sh create mode 100755 scripts/github_output.sh create mode 100755 scripts/korg-releases.py diff --git a/.github/actions/archive/action.yml b/.github/actions/archive/action.yml new file mode 100644 index 000000000..dc0636b5d --- /dev/null +++ b/.github/actions/archive/action.yml @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Archive results +description: Archive kdevops results in https://github.com/linux-kdevops/kdevops-results-archive.git +inputs: + ci_workflow: + required: false + type: string + default: 'demo' + dir: + description: 'Directory' + required: true + default: 'workdir' + +runs: + using: "composite" + steps: + - name: Get systemd journal files + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + make V=1 journal-dump + + - name: Build our kdevops archive results + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + make V=1 ci-archive CI_WORKFLOW="${{ inputs.ci_workflow }}" \ No newline at end of file diff --git a/.github/actions/cleanup/action.yml b/.github/actions/cleanup/action.yml new file mode 100644 index 000000000..18a8656aa --- /dev/null +++ b/.github/actions/cleanup/action.yml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Cleanup kdevops VMs +description: Destroy VMs and cleanup workspace + +inputs: + dir: + description: 'Directory' + required: true + default: 'workdir' + +runs: + using: "composite" + steps: + - name: Run kdevops make destroy + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + make destroy + + - name: Remove working-directory + shell: bash + run: | + rm --recursive --force --verbose ${{ inputs.dir }} diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml new file mode 100644 index 000000000..407c8ac3d --- /dev/null +++ b/.github/actions/setup/action.yml @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Setup kdevops +description: Setup kdevops workspace + +inputs: + ci_workflow: + required: false + type: string + default: 'demo' + dir: + description: 'Directory' + required: true + default: 'workdir' + kernel_tree: + required: false + type: string + default: 'linux' + kernel_ref: + required: false + type: string + default: 'master' + +runs: + using: "composite" + steps: + - name: Create workspace directory + shell: bash + run: | + set -euxo pipefail + rm --recursive --force --verbose ${{ inputs.dir }} + mkdir --parent --verbose ${{ inputs.dir }} + + - name: Configure git + shell: bash + run: | + set -euxo pipefail + git config --global --add safe.directory '*' + git config --global user.name "kdevops" + git config --global user.email "kdevops@lists.linux.dev" + + - name: Checkout kdevops + working-directory: ${{ inputs.dir }} + shell: bash + run: | + set -euxo pipefail + git clone https://github.com/dkruces/kdevops.git --branch main kdevops + + - name: Checkout custom branch with delta on kdevops/linux + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + LINUX_TREE="/mirror/${{ inputs.kernel_tree }}.git" + LINUX_TREE_REF="${{ inputs.kernel_ref }}" + git clone $LINUX_TREE linux + cd linux + git checkout $LINUX_TREE_REF + git log -1 + + - name: Make sure our repo kdevops defconfig exists + id: defconfig + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + KDEVOPS_DEFCONFIG=${{ inputs.ci_workflow }} + + if [[ ! -f defconfigs/$KDEVOPS_DEFCONFIG ]]; then + echo "Missing defconfig: defconfigs/$KDEVOPS_DEFCONFIG" + exit 1 + fi + + "${{ github.workspace }}/scripts/github_output.sh" \ + KDEVOPS_DEFCONFIG "$KDEVOPS_DEFCONFIG" + + - name: Initialize CI metadata for kdevops-results-archive for linux + id: metadata + working-directory: ${{ inputs.dir }}/kdevops/linux + shell: bash + run: | + set -euxo pipefail + echo "${{ inputs.kernel_tree }}" > ../ci.trigger + # Get the kdevops commit subject, not kernel commit + cd "${{ github.workspace }}" && git log -1 --pretty=format:"%s" > "${{ inputs.dir }}/kdevops/ci.subject" + cd - > /dev/null + echo "${{ inputs.kernel_ref }}" > ../ci.ref + + RELEVANT_GIT_TAG=$(cat ../ci.ref) + RELEVANT_GIT_REF=$(git rev-parse --short=12 $RELEVANT_GIT_TAG) + + "${{ github.workspace }}/scripts/github_output.sh" \ + LINUX_GIT_REF "$RELEVANT_GIT_REF" + "${{ github.workspace }}/scripts/github_output.sh" \ + LINUX_GIT_TAG "$RELEVANT_GIT_TAG" + + # Start out pessimistic + echo "unknown" > ../ci.result + echo "Nothing to write home about." > ../ci.commit_extra + + - name: Run kdevops make defconfig-repo + working-directory: ${{ inputs.dir }}/kdevops + env: + LINUX_GIT_TAG: ${{ steps.metadata.outputs.LINUX_GIT_TAG }} + LINUX_GIT_REF: ${{ steps.metadata.outputs.LINUX_GIT_REF }} + KDEVOPS_DEFCONFIG: ${{ steps.defconfig.outputs.KDEVOPS_DEFCONFIG }} + shell: bash + run: | + set -euxo pipefail + LINUX_TREE="/mirror/${{ inputs.kernel_tree }}.git" + LINUX_TREE_REF="$LINUX_GIT_TAG" + + # We make the compromise here to use a relevant git tag for the + # host prefix so that folks can easily tell what exact kernel tree + # is being tested by using the relevant git ref. That is, if you + # pushed a tree with the .github/ directory as the top of the tree, + # that commit will not be used, we'll use the last one as that is + # the relevant git ref we want to annotate a test for. + # + # The compromise here is that we expect no two same identical tests + # on the same self-hosted server. We could extend this with something + # like github.run_id but its not yet clear if we can have kdevops + # hosts with a bundled prefix ID like that ref-runid-testname. Tests + # have been done with the full lenght sha1sum though and we know that + # does work. + KDEVOPS_HOSTS_PREFIX="kci-$LINUX_GIT_REF" + + echo "Going to use defconfig-$KDEVOPS_DEFCONFIG" + + echo "Linux tree: $LINUX_TREE" + echo "Linux trigger ref: $LINUX_TREE_REF" + echo "Linux tag: $LINUX_GIT_TAG" + echo "Runner ID: ${{ github.run_id }}" + echo "kdevops host prefix: $KDEVOPS_HOSTS_PREFIX" + echo "kdevops defconfig: defconfig-$KDEVOPS_DEFCONFIG" + + # Customize KMOD_TIMEOUT when required + KMOD_TIMEOUT_ARG= + if [[ "$(hostname)" == *smc111* && \ + "$KDEVOPS_DEFCONFIG" == "linux-modules-kpd" ]]; then + KMOD_TIMEOUT_ARG="KMOD_TIMEOUT=222" + fi + + KDEVOPS_ARGS="\ + KDEVOPS_HOSTS_PREFIX=$KDEVOPS_HOSTS_PREFIX \ + LINUX_TREE=$LINUX_TREE \ + LINUX_TREE_REF=$LINUX_TREE_REF \ + ${KMOD_TIMEOUT_ARG} \ + defconfig-$KDEVOPS_DEFCONFIG" + echo "Going to run:" + echo "make $KDEVOPS_ARGS" + + make $KDEVOPS_ARGS + ./scripts/kconfig/merge_config.sh \ + -n .config \ + defconfigs/configs/diy.config \ + defconfigs/configs/ci.config + + - name: Run kdevops make + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + make -j$(nproc) + + - name: Run kdevops make bringup + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + ls -ld linux + make destroy + make bringup + + - name: Build linux and boot test nodes on test kernel + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + make linux + + - name: Build required ci tests + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + make ci-build-test CI_WORKFLOW=${{ inputs.ci_workflow }} diff --git a/.github/actions/test/action.yml b/.github/actions/test/action.yml new file mode 100644 index 000000000..a9022b4c0 --- /dev/null +++ b/.github/actions/test/action.yml @@ -0,0 +1,213 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Setup kdevops +description: Setup kdevops workspace + +inputs: + dir: + description: 'Directory' + required: true + default: 'workdir' + ci_workflow: + required: false + type: string + default: 'demo' + test_mode: + description: 'Testing mode' + required: false + default: 'full-testing' + tests: + description: 'Custom test to run (for kdevops-validation mode only)' + required: false + default: '' + +runs: + using: "composite" + steps: + - name: Run CI tests + id: ci_test + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + + # Create start time for duration calculation + echo "$(date +%s)" > ci.start_time + + # Handle test mode and tests parameters + if [[ -n "${{ inputs.tests }}" ]]; then + # Custom test specified for kdevops-validation mode + TESTS="${{ inputs.tests }}" + elif [[ "${{ inputs.test_mode }}" == "kdevops-validation" ]]; then + # Auto-assign appropriate test based on workflow + case "${{ inputs.ci_workflow }}" in + *fstests*|*xfs*|*btrfs*|*ext4*|*tmpfs*) + TESTS="generic/003" + ;; + blktests*) + TESTS="block/003" + ;; + selftests*|*modules*|*kmod*|*firmware*|*mm*) + TESTS="kmod/test_001" + ;; + *) + TESTS="generic/003" + ;; + esac + else + # Full testing mode - no TESTS variable + TESTS="" + fi + + # Export TESTS for current step and save to GitHub output for later steps + export TESTS="$TESTS" + "${{ github.workspace }}/scripts/github_output.sh" TESTS "$TESTS" + + make ci-test CI_WORKFLOW="${{ inputs.ci_workflow }}" + + - name: Generate workflow results path + id: setpath + shell: bash + run: | + set -euxo pipefail + + case "${{ inputs.ci_workflow }}" in + blktests*) wpath="workflows/blktests" ;; + *btrfs*) wpath="workflows/fstests" ;; + *ext4*) wpath="workflows/fstests" ;; + tmpfs*) wpath="workflows/fstests" ;; + *xfs*) wpath="workflows/fstests" ;; + *) wpath="workflows/selftests" ;; + esac + + "${{ github.workspace }}/scripts/github_output.sh" wpath "$wpath" + + - name: Generate CI commit info with workflow-specific logic + working-directory: ${{ inputs.dir }}/kdevops + shell: bash + run: | + set -euxo pipefail + + wpath="${{ steps.setpath.outputs.wpath }}" + + # Workflow-specific result collection + case "${{ inputs.ci_workflow }}" in + *xfs*|*btrfs*|*ext4*|tmpfs*|*fstests*) + # fstests workflows: Use xunit_results.txt for rich summary + echo "Collecting fstests results..." + if find "$wpath/results/last-run" -name "xunit_results.txt" -type f -exec cat {} \; > ci.commit_extra 2>/dev/null; then + echo "Found xunit_results.txt" + else + echo "No xunit_results.txt found, using fallback..." + echo "Kernel tests results:" > ci.commit_extra + find "$wpath/results/last-run/" -name '*.dmesg.log' -exec tail -n 1 {} + >> ci.commit_extra 2>/dev/null || true + fi + + # fstests success detection + if ! grep -E "failures, [1-9]|errors, [1-9]" ci.commit_extra >/dev/null 2>&1; then + echo "ok" > ci.result + else + echo "not ok" > ci.result + fi + ;; + blktests*) + # blktests workflows: Collect individual test results + + echo "Kernel tests results:" > ci.commit_extra + + # Collect test results from last-run directory + if [ -d "$wpath/results/last-run" ]; then + echo -e "\nBlktests summary:" >> ci.commit_extra + + # Count total tests by unique test names (not file extensions) + # Get base test names by removing extensions and extracting just the test name + test_names=$(find "$wpath/results/last-run" -type f -name "*" | \ + grep -E '/[^/]*$' | \ + sed 's|.*/||' | \ + sed 's/\.\(full\|out\|runtime\|dmesg\)$//' | \ + sort -u) + total_tests=$(echo "$test_names" | grep -v '^$' | wc -l) + + bad_files=$(find "$wpath/results/last-run" -name "*.out.bad" -type f) + if [[ -n "$bad_files" ]]; then + failed_tests=$(echo "$bad_files" | wc -l) + else + failed_tests=0 + fi + + passed_tests=$((total_tests - failed_tests)) + + echo "Tests run: $total_tests, Passed: $passed_tests, Failed: $failed_tests" >> ci.commit_extra + + # List failed tests if any + if [ $failed_tests -gt 0 ]; then + echo -e "\nFailed tests:" >> ci.commit_extra + find "$wpath/results/last-run" -name "*.out.bad" -type f | sed 's|.*/\([^/]*\)\.out\.bad$|\1|' | sort >> ci.commit_extra 2>/dev/null || true + fi + + # Show sample test status files for passed tests + if [ $passed_tests -gt 0 ]; then + echo -e "\nSample passed test:" >> ci.commit_extra + sample_files=$(find "$wpath/results/last-run" -type f -name "*" ! -name "*.out.bad" ! -name "*.dmesg") + sample_file=$(echo "$sample_files" | head -1) + if [ -n "$sample_file" ] && [ -f "$sample_file" ]; then + cat "$sample_file" >> ci.commit_extra || echo "Failed to read sample file" >> ci.commit_extra + else + echo "No valid sample file found" >> ci.commit_extra + fi + fi + else + echo -e "\nNo blktests results found in $wpath/results/last-run" >> ci.commit_extra + fi + + # blktests success detection - look for .out.bad files (failures) + bad_check=$(find "$wpath/results/last-run" -name "*.out.bad" -type f | head -1) + + if [ -n "$bad_check" ]; then + echo "not ok" > ci.result + else + echo "ok" > ci.result + fi + ;; + *selftests*|*modules*|*mm*|*firmware*) + # selftests workflows: Use userspace.log + echo "Kernel tests results:" > ci.commit_extra + find "$wpath/results/last-run/" -name '*.userspace.log' -exec cat {} \; >> ci.commit_extra 2>/dev/null || true + + # selftests success detection + if grep -q "passed\|PASS" ci.commit_extra && ! grep -q "FAIL\|failed\|ERROR" ci.commit_extra; then + echo "ok" > ci.result + else + echo "not ok" > ci.result + fi + ;; + *) + # Default/unknown workflows: Generic approach + echo "Kernel tests results:" > ci.commit_extra + find "$wpath/results/last-run/" -name '*.dmesg.log' -exec tail -n 1 {} + >> ci.commit_extra 2>/dev/null || true + echo -e "\n\nUserspace test results:" >> ci.commit_extra + find "$wpath/results/last-run/" -name '*.userspace.log' -exec tail -n 1 {} + >> ci.commit_extra 2>/dev/null || true + + if grep -i -q "fail" ci.commit_extra; then + echo "not ok" > ci.result + else + echo "ok" > ci.result + fi + ;; + esac + + # Set environment variables for the commit message script + export CI_WORKFLOW="${{ inputs.ci_workflow }}" + export KERNEL_TREE="${{ inputs.kernel_tree || 'linux' }}" + + # Get TESTS from GitHub output (determines kdevops validation vs full testing) + TESTS="${{ steps.ci_test.outputs.TESTS }}" + if [[ -n "${TESTS:-}" ]]; then + export TESTS="$TESTS" + fi + + # Generate enhanced commit message using our script + ./scripts/generate_ci_commit_message.sh > ci.commit_message_enhanced + + # Keep the original ci.commit_extra for backward compatibility + # The archive action will use ci.commit_message_enhanced if available diff --git a/.github/workflows/destroy.yml b/.github/workflows/destroy.yml new file mode 100644 index 000000000..a70257b44 --- /dev/null +++ b/.github/workflows/destroy.yml @@ -0,0 +1,69 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Run kdevops Destroyer - Manual + +on: + workflow_dispatch: + inputs: + ci_workflow: + description: "CI Workflow" + required: true + default: 'blktests_nvme' + type: choice + options: + - blktests + - blktests_block + - blktests_loop + - blktests_meta + - blktests_nbd + - blktests_nvme + - blktests_nvmemp + - blktests_scsi + - blktests_srp + - blktests_zbd + - tmpfs + - tmpfs_default + - tmpfs_huge + - tmpfs_noswap + - linux-btrfs-kpd + - linux-ext4-kpd + - linux-firmware-kpd + - linux-mm-kpd + - linux-modules-kpd + - linux-xfs-kpd + - selftests + host_prefix: + description: "Host prefix" + required: true + default: 'debian13' + type: string + +jobs: + destroyer: + name: Destroy guests + runs-on: [self-hosted] + steps: + - name: Create workdir + run: | + rm --recursive --force --verbose ${{ inputs.ci_workflow }} + mkdir --parent --verbose ${{ inputs.ci_workflow }} + + - name: Checkout kdevops + run: | + cd ${{ inputs.ci_workflow }} + git clone https://github.com/dkruces/kdevops.git --branch ci-workflow kdevops + + - name: Run kdevops make defconfig-repo + run: | + set -euxo pipefail + + sudo virsh list --all + + cd ${{ inputs.ci_workflow }}/kdevops + make \ + KDEVOPS_HOSTS_PREFIX="${{ inputs.host_prefix }}" \ + defconfig-${{ inputs.ci_workflow }} + + make AV=1 V=1 destroy + + sudo virsh list --all diff --git a/.github/workflows/fstests.yml b/.github/workflows/fstests.yml deleted file mode 100644 index e13978c72..000000000 --- a/.github/workflows/fstests.yml +++ /dev/null @@ -1,98 +0,0 @@ -name: Run kdevops on self-hosted runner - -on: - push: - branches: - - '**' - pull_request: - branches: - - '**' - workflow_dispatch: # Add this for manual triggering of the workflow - -jobs: - run-kdevops: - name: Run kdevops CI - runs-on: [self-hosted, Linux, X64] - steps: - - name: Checkout repository - uses: actions/checkout@v4 - - - name: Set CI metadata for kdevops-results-archive - run: | - echo "$(basename ${{ github.repository }})" > ci.trigger - git log -1 --pretty=format:"%s" > ci.subject - # Start out pessimistic - echo "not ok" > ci.result - echo "Nothing to write home about." > ci.commit_extra - - - name: Set kdevops path - run: echo "KDEVOPS_PATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV - - - name: Configure git - run: | - git config --global --add safe.directory '*' - git config --global user.name "kdevops" - git config --global user.email "kdevops@lists.linux.dev" - - - name: Run kdevops make defconfig-repo - run: | - KDEVOPS_TREE_REF="${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}" - SHORT_PREFIX="$(echo ${KDEVOPS_TREE_REF:0:12})" - make KDEVOPS_HOSTS_PREFIX="$SHORT_PREFIX" \ - ANSIBLE_CFG_CALLBACK_PLUGIN="debug" \ - LINUX_TREE_REF="v6.15" \ - defconfig-xfs_reflink_4k - - - name: Run kdevops make - run: | - make V=1 -j$(nproc) - - - name: Run kdevops make bringup - run: | - make V=1 bringup - - - name: Build linux and boot test nodes on test kernel - run: | - make V=1 linux - - - name: Build fstests - run: | - make V=1 fstests - - - name: Run just one fstest to verify we tests and test collection works - run: | - make V=1 fstests-baseline TESTS=generic/003 - echo "ok" > ci.result - find workflows/fstests/results/last-run -name xunit_results.txt -type f -exec cat {} \; > ci.commit_extra || true - if ! grep -E "failures, [1-9]|errors, [1-9]" ci.commit_extra; then - echo "ok" > ci.result - fi - - - name: Get systemd journal files - if: always() # This ensures the step runs even if previous steps failed - run: | - make V=1 journal-dump - - - name: Start SSH Agent - if: always() # Ensure this step runs even if previous steps failed - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - - name: Build our kdevops archive results - if: always() # This ensures the step runs even if previous steps failed - run: | - make V=1 ci-archive - - - name: Upload our kdevops results archive - if: always() # This ensures the step runs even if previous steps failed - uses: actions/upload-artifact@v4 - with: - name: kdevops-ci-results - path: ${{ env.KDEVOPS_PATH }}/archive/*.zip - - # Ensure make destroy always runs, even on failure - - name: Run kdevops make destroy - if: always() # This ensures the step runs even if previous steps failed - run: | - make V=1 destroy diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..a6f3ae042 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,97 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Run kdevops CI Workflow - Reusable + +on: + workflow_call: + inputs: + ci_workflow: + description: "CI Workflow" + required: true + default: 'blktests_nvme' + type: string + kernel_ref: + description: "Linux tree git reference (branch/tag/commit-id)" + required: true + default: 'master' + type: string + kernel_tree: + description: "Linux kernel tree to use" + required: true + default: 'linux' + type: string + test_mode: + description: 'Testing mode' + required: false + default: 'full-testing' + type: string + tests: + description: 'Custom test to run (for kdevops-validation mode only)' + required: false + default: '' + type: string + +jobs: + setup: + name: Setup kdevops workspace + runs-on: [self-hosted] + steps: + - name: Checkout dkruces/kdevops + uses: actions/checkout@v4 + with: + clean: false + + - name: kdevops setup + uses: ./.github/actions/setup + with: + ci_workflow: ${{ inputs.ci_workflow }} + dir: ${{ inputs.ci_workflow }} + kernel_ref: ${{ inputs.kernel_ref }} + kernel_tree: ${{ inputs.kernel_tree }} + + test: + name: Run kdevops ci-test + runs-on: [self-hosted] + needs: [setup] + timeout-minutes: 60 + steps: + - name: kdevops ci-test + uses: ./.github/actions/test + with: + ci_workflow: ${{ inputs.ci_workflow }} + dir: ${{ inputs.ci_workflow }} + test_mode: ${{ inputs.test_mode }} + tests: ${{ inputs.tests }} + + archive: + name: Archive kdevops + runs-on: [self-hosted] + needs: [setup, test] + steps: + - name: Start SSH Agent + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Archive ci-test results + uses: ./.github/actions/archive + with: + ci_workflow: ${{ inputs.ci_workflow }} + dir: ${{ inputs.ci_workflow }} + + - name: Upload our kdevops results archive + uses: actions/upload-artifact@v4 + with: + name: kdevops-ci-results + path: ${{ inputs.ci_workflow }}/kdevops/archive/*.zip + + cleanup: + name: Cleanup kdevops workspace + runs-on: [self-hosted] + needs: [setup, test, archive] + if: always() + steps: + - name: kdevops cleanup + uses: ./.github/actions/cleanup + with: + dir: ${{ inputs.ci_workflow }} diff --git a/.github/workflows/manual.yml b/.github/workflows/manual.yml new file mode 100644 index 000000000..7cf787b9c --- /dev/null +++ b/.github/workflows/manual.yml @@ -0,0 +1,133 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Run kdevops CI Workflow - Manual + +on: + workflow_dispatch: + inputs: + ci_workflow: + description: "CI Workflow" + required: true + default: 'blktests_nvme' + type: choice + options: + - blktests + - blktests_block + - blktests_loop + - blktests_meta + - blktests_nbd + - blktests_nvme + - blktests_nvmemp + - blktests_scsi + - blktests_srp + - blktests_zbd + - tmpfs + - tmpfs_default + - tmpfs_huge + - tmpfs_noswap + - linux-btrfs-kpd + - linux-ext4-kpd + - linux-firmware-kpd + - linux-mm-kpd + - linux-modules-kpd + - linux-xfs-kpd + - xfs + - xfs_crc + - xfs_crc_logdev + - xfs_crc_rtdev + - xfs_crc_rtdev_extsize_28k + - xfs_crc_rtdev_extsize_64k + - xfs_nocrc + - xfs_nocrc_16k_4ks + - xfs_nocrc_1k + - xfs_nocrc_2k + - xfs_nocrc_32_4ks + - xfs_nocrc_4k + - xfs_nocrc_512 + - xfs_nocrc_64_4ks + - xfs_nocrc_8k_4ks + - xfs_nocrc_lbs + - xfs_reflink + - xfs_reflink_1024 + - xfs_reflink_16k_4ks + - xfs_reflink_2k + - xfs_reflink_32k_4ks + - xfs_reflink_4k + - xfs_reflink_4k_8ks + - xfs_reflink_64k_4ks + - xfs_reflink_dir_bsize_8k + - xfs_reflink_lbs + - xfs_reflink_logdev + - xfs_reflink_normapbt + - xfs_reflink_nrext64 + - xfs_reflink_stripe_len + - xfs-soak + - lbs-xfs + - lbs-xfs-bdev-large-nvme + - lbs-xfs-bdev-nvme + - lbs-xfs-small + - selftests + kernel_tree: + description: "Linux kernel tree to use" + required: true + default: 'linux' + type: choice + options: + - linux + - linux-next + - linux-stable + kernel_ref: + description: "Linux tree git reference (branch/tag/commit-id)" + required: true + default: 'master' + type: string + test_mode: + description: 'Testing mode' + required: false + default: 'full-testing' + type: choice + options: + - 'full-testing' # Complete test suite + - 'kdevops-validation' # Single test for framework validation + tests: + description: 'Custom test to run (for kdevops-validation mode only)' + required: false + type: string + default: '' + +jobs: + check_ref: + name: Check Linux kernel Git Reference + runs-on: [self-hosted] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Check kernel_ref exists + id: check_kernel_ref + run: | + set -euxo pipefail + + ref="${{ github.event.inputs.kernel_ref }}" + tree="${{ github.event.inputs.kernel_tree }}" + mirror="/mirror/${tree}.git" + ls_remote="$(git ls-remote "$mirror" "refs/*/${ref}" || true)" + contains_branch="$(git -C "$mirror" branch --contains "${ref}" || true)" + contains_tag="$(git -C "$mirror" branch --contains "${ref}" || true)" + + if [ -z "$ls_remote" ] && [ -z "$contains_branch" ] && [ -z "$contains_tag" ]; then + echo "Linux kernel ${ref} does not exist." + exit 1 + fi + + manual: + name: Manual kdevops CI + needs: [check_ref] + uses: ./.github/workflows/main.yml + secrets: inherit + with: + ci_workflow: ${{ inputs.ci_workflow }} + kernel_ref: ${{ inputs.kernel_ref }} + kernel_tree: ${{ inputs.kernel_tree }} + test_mode: ${{ inputs.test_mode }} + tests: ${{ inputs.tests }} diff --git a/.github/workflows/schedule.yml b/.github/workflows/schedule.yml new file mode 100644 index 000000000..873e90b04 --- /dev/null +++ b/.github/workflows/schedule.yml @@ -0,0 +1,53 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Run kdevops CI Workflow - Schedule + +on: + schedule: + - cron: '0 14 * * *' + + workflow_dispatch: + +jobs: + check_ref: + name: Check Linux kernel Git Reference + outputs: + kernel_ref: ${{ steps.check_kernel_ref.outputs.kernel_ref }} + kernel_tree: ${{ steps.check_kernel_ref.outputs.kernel_tree }} + runs-on: [self-hosted] + steps: + - name: Checkout repo + uses: actions/checkout@v4 + + - name: Generate kernel_ref + id: check_kernel_ref + run: | + set -euxo pipefail + day_of_week=$(date +%u) + + kernel_ref=$(./scripts/korg-releases.py --moniker linux-next) + kernel_tree=linux-next + if [ "$day_of_week" -eq 1 ]; then + kernel_ref=$(./scripts/korg-releases.py --moniker mainline) + kernel_tree=linux + fi + + "${{ github.workspace }}/scripts/github_output.sh" kernel_ref "$kernel_ref" + "${{ github.workspace }}/scripts/github_output.sh" kernel_tree "$kernel_tree" + + schedule: + name: Scheduled kdevops CI + needs: [check_ref] + uses: ./.github/workflows/main.yml + secrets: inherit + strategy: + matrix: + ci_workflow: + - blktests + - linux-mm-kpd + - linux-modules-kpd + - tmpfs + with: + ci_workflow: ${{ matrix.ci_workflow }} + kernel_ref: ${{ needs.check_ref.outputs.kernel_ref }} + kernel_tree: ${{ needs.check_ref.outputs.kernel_tree }} diff --git a/scripts/generate_ci_commit_message.sh b/scripts/generate_ci_commit_message.sh new file mode 100755 index 000000000..8be2c0b3f --- /dev/null +++ b/scripts/generate_ci_commit_message.sh @@ -0,0 +1,263 @@ +#!/bin/bash +# SPDX-License-Identifier: copyleft-next-0.3.1 + +# Generate enhanced CI commit message for kdevops-results-archive +# This script creates commit messages following the kdevops CI format specification + +set -euo pipefail + +# Default values +CI_WORKFLOW="${CI_WORKFLOW:-demo}" +KERNEL_TREE="${KERNEL_TREE:-linux}" +TESTS_PARAM="${TESTS:-${LIMIT_TESTS:-}}" +DURATION="${DURATION:-unknown}" + +# Input files (expected to be created by GitHub Actions) +CI_COMMIT_EXTRA="${CI_COMMIT_EXTRA:-ci.commit_extra}" +CI_RESULT="${CI_RESULT:-ci.result}" +CI_START_TIME="${CI_START_TIME:-ci.start_time}" +CI_REF="${CI_REF:-ci.ref}" +CI_TRIGGER="${CI_TRIGGER:-ci.trigger}" + +# Helper function to wrap long commit subjects at 72 chars +wrap_commit_subject() { + local subject="$1" + if [ ${#subject} -le 68 ]; then # 68 to account for quotes + echo "\"$subject\"" + else + echo "\"$subject\"" | fold -s -w 68 | sed '2,$s/^/ /' + fi +} + +# Calculate duration if start time available +calculate_duration() { + if [ -f "$CI_START_TIME" ]; then + local start_time=$(cat "$CI_START_TIME" 2>/dev/null || echo $(date +%s)) + local end_time=$(date +%s) + local duration_sec=$((end_time - start_time)) + local minutes=$((duration_sec / 60)) + local seconds=$((duration_sec % 60)) + + if [ $minutes -gt 0 ]; then + echo "${minutes}m ${seconds}s" + else + echo "${seconds}s" + fi + else + echo "unknown" + fi +} + +# Determine scope and header format with enhanced context-aware detection +determine_scope() { + # Simple detection: if TESTS is set, it's kdevops validation + if [[ -n "${TESTS:-}" ]] || [[ -n "${LIMIT_TESTS:-}" ]] || [[ -n "${TESTS_PARAM:-}" ]]; then + echo "kdevops" + else + echo "tests" + fi +} + +# Get kernel information +get_kernel_info() { + # Try to get git describe from linux directory, fallback gracefully + local kernel_describe="unknown" + local kernel_hash="unknown" + local kernel_subject="unknown commit" + + if [ -d "linux/.git" ]; then + kernel_describe=$(cd linux && git describe --tags --always --dirty 2>/dev/null || echo "unknown") + kernel_hash=$(cd linux && git rev-parse --short=12 HEAD 2>/dev/null || echo "unknown") + kernel_subject=$(cd linux && git log -1 --pretty=format:"%s" 2>/dev/null || echo "unknown commit") + fi + + echo "$kernel_describe|$kernel_hash|$kernel_subject" +} + +# Get kdevops information (from kdevops directory context) +get_kdevops_info() { + # This will be run from the kdevops directory + local kdevops_hash=$(git rev-parse --short=12 HEAD 2>/dev/null || echo "unknown") + local kdevops_subject=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "unknown commit") + + echo "$kdevops_hash|$kdevops_subject" +} + +# Read test results +get_test_results() { + local result_content="" + local test_result="unknown" + + if [ -f "$CI_COMMIT_EXTRA" ]; then + result_content=$(cat "$CI_COMMIT_EXTRA") + else + result_content="No test results available." + fi + + if [ -f "$CI_RESULT" ]; then + test_result=$(cat "$CI_RESULT" | tr -d '\n' | tr -d '\r') + fi + + echo "$test_result|$result_content" +} + +# Determine workflow type for result formatting +get_workflow_type() { + case "$CI_WORKFLOW" in + *xfs*|*btrfs*|*ext4*|*tmpfs*|*lbs-xfs*) echo "fstests" ;; + blktests*) echo "blktests" ;; + selftests*|*firmware*|*modules*|*mm*) echo "selftests" ;; + ai_*) echo "ai" ;; + mmtests_*) echo "mmtests" ;; + ltp_*) echo "ltp" ;; + fio-tests*|fio_*) echo "fio-tests" ;; + minio_*) echo "minio" ;; + *) echo "other" ;; + esac +} + +# Generate the enhanced commit message +generate_commit_message() { + local scope=$(determine_scope) + local workflow_type=$(get_workflow_type) + local duration=$(calculate_duration) + + # Get kernel tree and ref from metadata files + local actual_kernel_tree="$KERNEL_TREE" + local actual_kernel_ref="unknown" + + if [ -f "$CI_TRIGGER" ]; then + actual_kernel_tree=$(cat "$CI_TRIGGER" | tr -d '\n' | tr -d '\r') + fi + + if [ -f "$CI_REF" ]; then + actual_kernel_ref=$(cat "$CI_REF" | tr -d '\n' | tr -d '\r') + fi + + # Get kernel info (this assumes we're in the kernel directory context) + local kernel_info=$(get_kernel_info) + local kernel_describe=$(echo "$kernel_info" | cut -d'|' -f1) + local kernel_hash=$(echo "$kernel_info" | cut -d'|' -f2) + local kernel_subject=$(echo "$kernel_info" | cut -d'|' -f3) + + # Use metadata ref if available, fallback to git describe + # Ensure we use the most accurate kernel version consistently + if [ "$actual_kernel_ref" != "unknown" ] && [ "$actual_kernel_ref" != "" ]; then + kernel_describe="$actual_kernel_ref" + fi + + # Debug kernel version information + + # Get kdevops info (this assumes we can access kdevops directory) + local kdevops_info=$(get_kdevops_info) + local kdevops_hash=$(echo "$kdevops_info" | cut -d'|' -f1) + local kdevops_subject=$(echo "$kdevops_info" | cut -d'|' -f2) + + # Get test results + local results_info=$(get_test_results) + local test_result=$(echo "$results_info" | cut -d'|' -f1) + local result_content=$(echo "$results_info" | cut -d'|' -f2-) + + # Build header + local header="" + if [ "$scope" = "kdevops" ]; then + # kdevops-ci validation format: focus on kdevops commit being tested + header="kdevops-ci: $CI_WORKFLOW: $kdevops_hash $kdevops_subject" + else + # Full test suite format: include PASS/FAIL status + local status="PASS" + if [ "$test_result" = "not ok" ] || [ "$test_result" = "fail" ]; then + status="FAIL" + fi + header="$CI_WORKFLOW ($actual_kernel_tree $kernel_describe): $status" + fi + + # Build scope description + local scope_desc="" + if [ "$scope" = "kdevops" ]; then + local test_param="${TESTS:-${LIMIT_TESTS:-${TESTS_PARAM:-}}}" + if [[ -n "$test_param" ]]; then + scope_desc="kdevops validation (single test: $test_param)" + else + scope_desc="kdevops validation" + fi + else + scope_desc="full test suite" + fi + + # Wrap kernel commit subject + local wrapped_kernel_subject=$(wrap_commit_subject "$kernel_subject") + local wrapped_kdevops_subject=$(wrap_commit_subject "$kdevops_subject") + + # Generate metadata line with 72-char handling + local metadata_line="workflow: $CI_WORKFLOW | tree: $actual_kernel_tree | ref: $kernel_describe | scope: $scope | result: $test_result" + local metadata_output="" + if [ ${#metadata_line} -gt 72 ]; then + metadata_output="workflow: $CI_WORKFLOW | tree: $actual_kernel_tree"$'\n'"ref: $kernel_describe | scope: $scope | result: $test_result" + else + metadata_output="$metadata_line" + fi + + # Build kernel validation info + local kernel_info_line="" + if [ "$actual_kernel_ref" != "unknown" ] && [ "$kernel_describe" != "$actual_kernel_ref" ]; then + # Show both requested and actual if they differ + kernel_info_line=" Kernel: $actual_kernel_ref (requested) → $kernel_describe (actual)" + else + # Show just the kernel version if they match or no metadata + kernel_info_line=" Kernel: $kernel_describe" + fi + + # Build the complete commit message based on scope + if [ "$scope" = "kdevops" ]; then + # kdevops-ci validation format + cat << EOF +$header + +BUILD INFO: + kdevops: $kdevops_hash ($wrapped_kdevops_subject) + Workflow: $CI_WORKFLOW + Test: ${TESTS_PARAM:-"complete suite"} +$kernel_info_line + Duration: $duration + +EXECUTION RESULTS: +$result_content + +METADATA: +workflow: $CI_WORKFLOW | scope: kdevops | test: ${TESTS_PARAM:-"complete suite"} | requested: $actual_kernel_ref | actual: $kernel_describe | result: $test_result +EOF + else + # Full test suite format + cat << EOF +$header + +BUILD INFO: +$kernel_info_line ($wrapped_kernel_subject) + Workflow: $CI_WORKFLOW + kdevops: $kdevops_hash ($wrapped_kdevops_subject) + Scope: $scope_desc + Duration: $duration + +EXECUTION RESULTS: +$result_content + +METADATA: +$metadata_output | requested: $actual_kernel_ref | actual: $kernel_describe +EOF + fi +} + +# Main execution +main() { + # Validate required environment + if [ -z "$CI_WORKFLOW" ]; then + echo "Error: CI_WORKFLOW environment variable is required" >&2 + exit 1 + fi + + generate_commit_message +} + +# Run main function +main "$@" \ No newline at end of file diff --git a/scripts/github_output.sh b/scripts/github_output.sh new file mode 100755 index 000000000..711306c38 --- /dev/null +++ b/scripts/github_output.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +# +# Usage: ./github_output.sh key value +set -euxo pipefail + +key="$1" +value="$2" + +echo "$key=$value" >> "$GITHUB_OUTPUT" diff --git a/scripts/korg-releases.py b/scripts/korg-releases.py new file mode 100755 index 000000000..de494327e --- /dev/null +++ b/scripts/korg-releases.py @@ -0,0 +1,94 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: copyleft-next-0.3.1 +import sys +import logging +import argparse +import json +import urllib.request +import socket +import re + + +def parser(): + parser = argparse.ArgumentParser(description="kernel.org/releases.json checker") + parser.add_argument("--debug", action="store_true", help="debug") + parser.add_argument( + "--moniker", + help="moniker (mainline, stable, longterm or linux-next)", + required=True, + ) + parser.add_argument( + "--pname", + help="project name for User-Agent request", + default="kdevops", + ) + parser.add_argument( + "--pversion", + help="project version for User-Agent request", + default="5.0.2", + ) + return parser + + +def _check_connection(host, port, timeout=2): + try: + with socket.create_connection((host, port), timeout): + logging.debug(f"Connection to {host} on port {port} succeeded!") + return True + except (socket.timeout, socket.error) as e: + logging.debug(f"Connection to {host} on port {port} failed: {e}") + return False + + +def kreleases(args) -> None: + """Get the latest kernel releases from kernel.org/releases.json""" + + reflist = [] + if _check_connection("kernel.org", 80): + _url = "https://www.kernel.org/releases.json" + req = urllib.request.Request( + _url, + headers={ + "User-Agent": f"{args.pname}/{args.pversion} (kdevops@lists.linux.dev)" + }, + ) + with urllib.request.urlopen(req) as url: + data = json.load(url) + + for release in data["releases"]: + if release["moniker"] == args.moniker: + # Check if release.json is aa.bb.cc type + if re.compile(r"^\d+\.\d+(\.\d+|-rc\d+)?$").match( + release["version"] + ): + reflist.append("v" + release["version"]) + else: + reflist.append(release["version"]) + + logging.debug(f"{reflist}") + for r in reflist: + print(r) + + +def main() -> None: + """Kconfig choice generator for git refereces""" + log = logging.getLogger() + log.setLevel(logging.INFO) + p = parser() + args, _ = p.parse_known_args() + if args.debug: + log.setLevel(logging.DEBUG) + + kreleases(args) + + +if __name__ == "__main__": + ret = 0 + try: + main() + except Exception: + ret = 1 + import traceback + + traceback.print_exc() + sys.exit(ret) From 40dd4c255cadc6b936f507035e8b21a2f54f2271 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 15:18:24 +0200 Subject: [PATCH 13/14] github: add push/PR workflow support with fixed validation tests Add new push.yml workflow that triggers on push and pull_request events with fixed validation testing parameters: - Fixed kernel: v6.15 on linux tree - Fixed workflows: blktests_nvme and xfs_reflink_4k - Fixed mode: kdevops-validation (single test execution) - Auto-assigned tests: block/003 and generic/003 respectively Also disable docker-tests.yml workflow (rename to .disabled) to reduce CI noise and add push-test.yml for debugging push/PR triggers. The push.yml workflow follows the same architecture as schedule.yml and manual.yml by calling the reusable main.yml workflow, enabling fast validation CI runs on every push and PR. Generated-by: Claude AI Signed-off-by: Daniel Gomez --- ...er-tests.yml => docker-tests.yml.disabled} | 0 .github/workflows/push-test.yml | 22 +++++++++++++++ .github/workflows/push.yml | 28 +++++++++++++++++++ 3 files changed, 50 insertions(+) rename .github/workflows/{docker-tests.yml => docker-tests.yml.disabled} (100%) create mode 100644 .github/workflows/push-test.yml create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/docker-tests.yml b/.github/workflows/docker-tests.yml.disabled similarity index 100% rename from .github/workflows/docker-tests.yml rename to .github/workflows/docker-tests.yml.disabled diff --git a/.github/workflows/push-test.yml b/.github/workflows/push-test.yml new file mode 100644 index 000000000..26446bd47 --- /dev/null +++ b/.github/workflows/push-test.yml @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Test Push/PR Trigger + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + test: + name: Test Job + runs-on: [self-hosted] + steps: + - name: Test step + run: | + echo "Push/PR trigger is working!" + echo "Event: ${{ github.event_name }}" + echo "Ref: ${{ github.ref }}" \ No newline at end of file diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..f28fb27ce --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: GPL-2.0 +--- +name: Run kdevops CI Workflow - Push/PR + +on: + push: + branches: + - '**' + pull_request: + branches: + - '**' + +jobs: + push-pr: + name: Push/PR kdevops CI + uses: ./.github/workflows/main.yml + secrets: inherit + strategy: + matrix: + ci_workflow: + - blktests_nvme + - xfs_reflink_4k + with: + ci_workflow: ${{ matrix.ci_workflow }} + kernel_ref: 'v6.15' + kernel_tree: 'linux' + test_mode: 'kdevops-validation' + tests: '' From 04a828b0d3c495e8dd2d548540b7e0ab47909540 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Tue, 23 Sep 2025 14:57:45 +0200 Subject: [PATCH 14/14] README: test Signed-off-by: Daniel Gomez --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index adc044f60..81a3f4f21 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +Testing + Table of Contents =================