From e920af396cce7496c45c5762cc362f0915de3dfa Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 18:33:59 +1000 Subject: [PATCH 1/9] Look for dnf5 repos in /etc/dnf/repos.d openSUSE's dnf5 package adds /etc/dnf/repos.d to the default repo directory list. So this makes cnf search that as well. --- src/main.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index fe9cebe..86aaaa9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,7 +16,9 @@ mod pool; const ZYPPER_REPO_GLOB: &str = "/etc/zypp/repos.d/*.repo"; // Default value of the reposdir configuration directory -const DNF5_REPOS_GLOBS: [&str; 3] = [ +// (Note that /etc/dnf/repos.d is patched in by the openSUSE dnf5 package) +const DNF5_REPOS_GLOBS: [&str; 4] = [ + "/etc/dnf/repos.d/*.repo", "/etc/yum.repos.d/*.repo", "/etc/distro.repos.d/*.repo", "/usr/share/dnf5/repos.d/*.repo", From 2a1fc902488c187b204ce3d4864b116ba0b7fc94 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 19:50:13 +1000 Subject: [PATCH 2/9] Check if zypper is installed. This gives an error if it can't detect a supported package manager. --- src/main.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 86aaaa9..462a6f8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,8 +72,11 @@ fn main() { let pm = if Path::exists(Path::new("/usr/bin/dnf5")) { // Use DNF5 if it's installed (this should probably be set via a config file, in case you have both installed but prefer zypper) PackageManager::Dnf5 - } else { + } else if Path::exists(Path::new("/usr/bin/zypper")) { PackageManager::Zypper + } else { + println!("Neither /usr/bin/dnf5 nor /usr/bin/zypper could be found."); + exit(127); }; let repos = match load_repos(pm) { From 7445f29c8c61ef8769cacd882320fb6992f51be8 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 20:02:00 +1000 Subject: [PATCH 3/9] Updated documentation examples. This just makes the examples in the documentation match the current output format. --- README.md | 28 +++++++++++++++++----------- cnf.1 | 4 ++-- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index ccafb95..532a0de 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,8 @@ To query not installed programs: ``` > ```.log > The program 'cmake' can be found in following packages: -> * cmake-full [ path: /usr/bin/cmake, repository: zypp (repo-oss) ] -> * cmake-mini [ path: /usr/bin/cmake, repository: zypp (repo-oss) ] +> * cmake-full [ path: /usr/bin/cmake, repository: repo-oss ] +> * cmake-mini [ path: /usr/bin/cmake, repository: repo-oss ] > > Try installing with: > sudo zypper install @@ -66,8 +66,8 @@ cmake ``` > ```.log > The program 'cmake' can be found in following packages: -> * cmake-full [ path: /usr/bin/cmake, repository: zypp (repo-oss) ] -> * cmake-mini [ path: /usr/bin/cmake, repository: zypp (repo-oss) ] +> * cmake-full [ path: /usr/bin/cmake, repository: repo-oss ] +> * cmake-mini [ path: /usr/bin/cmake, repository: repo-oss ] > > Try installing with: > sudo zypper install @@ -85,13 +85,19 @@ The testing itself is wrapped in [bats](https://github.com/bats-core/bats-core) > ```.log > test.bats > ✓ root: installed /usr/bin/rpm -> ✓ root: installed /usr/sbin/sysctl -> ✓ root: not installed single package -> ✓ root: not installed more packages -> ✓ root: bash handler: not installed more packages -> ✓ nonroot: not installed more packages -> -> 6 tests, 0 failures +> > ✓ root: installed /usr/sbin/sysctl +> ✓ root: not installed xnake +> ✓ root: not installed make +> ✓ root: not installed cmake +> ✓ nonroot: not installed cmake +> ✓ nonroot: bash without handler: not installed cmake +> ✓ nonroot: bash handler: not installed cmake +> ✓ nonroot: zsh without handler: not installed cmake +> ✓ nonroot: zsh handler: not installed cmake +> ✓ nonroot: fish handler: not installed cmake +> ✓ issue26: do not list not installable files +> +> 12 tests, 0 failures > ``` Every test can be executed on a command line. The `root.sh` wrapper mounts the diff --git a/cnf.1 b/cnf.1 index a2808e9..f27f5ab 100644 --- a/cnf.1 +++ b/cnf.1 @@ -16,8 +16,8 @@ packages. Simply typing missing command executes the handler. cmake The program 'cmake' can be found in following packages: - * cmake-full [ path: /usr/bin/cmake, repository: zypp (repo-oss) ] - * cmake-mini [ path: /usr/bin/cmake, repository: zypp (repo-oss) ] + * cmake-full [ path: /usr/bin/cmake, repository: repo-oss ] + * cmake-mini [ path: /usr/bin/cmake, repository: repo-oss ] Try installing with: sudo zypper install From 6e4b36d9c0f7a8b6c65801da9ede12b09dfa90b0 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 20:02:00 +1000 Subject: [PATCH 4/9] Updated documentation to mention dnf5 support The man page also instructs you on how to update the repository chache that is queried. --- README.md | 1 + cnf.1 | 3 +++ 2 files changed, 4 insertions(+) diff --git a/README.md b/README.md index 532a0de..31d60fb 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ | **`zsh` integration** | Yes | Yes | | **Disable integration** | Uninstall package | Magic variable | | **Localization** | Yes (UTF-8 only) | Yes | +| **Package Managers Queried** | Dnf5, Zypper | Zypper | ## **Build** diff --git a/cnf.1 b/cnf.1 index f27f5ab..a4855aa 100644 --- a/cnf.1 +++ b/cnf.1 @@ -9,6 +9,9 @@ cnf \- A command not found handler for openSUSE libsolv(3) under the hood. The source of truth of information about repositories, packages and their content. .PP +This works with enabled dnf5 or zypper repostiores: the first one found in /usr/bin will be used. +Use dnf5 \-\-refresh makecache or zypper \-\-force refresh to update the metadata used. +.PP It is typically called through command-not-found mechanism of bash(1) or zsh(1) or fish(1). This is enabled via installing the cnf-bash or cnf-zsh packages. Simply typing missing command executes the handler. From df878e8aeff9731dae2fc7ebe443aa6e5f705007 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 21:58:12 +1000 Subject: [PATCH 5/9] Added instructions for .zsh This also modifies the zsh file to use the COMMAND_NOT_FOUND_BIN variable, just like the bash one. --- README.md | 4 ++-- command_not_found.zsh | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 31d60fb..0edab3e 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,10 @@ To query installed programs in `/usr/bin`: > Absolute path to 'vim' is '/usr/bin/vim'. Please check your $PATH variable to see whether it contains the mentioned path > ``` -## **Integrate with `bash`** +## **Integrate with `bash` and `zsh`** ```.sh -source command_not_found_bash +source command_not_found.bash # or command_not_found.zsh export COMMAND_NOT_FOUND_BIN=./target/debug/cnf cmake ``` diff --git a/command_not_found.zsh b/command_not_found.zsh index 5579fdb..0a34b11 100644 --- a/command_not_found.zsh +++ b/command_not_found.zsh @@ -5,10 +5,11 @@ # - one to be generic command_not_found_handler() and to be hooked # - one to be available when command_not_found_handler() is redefined function command_not_found_handler cnf_handler { - if [ -x /usr/bin/cnf ]; then + local cnf_bin=${COMMAND_NOT_FOUND_BIN:-/usr/bin/cnf} + if [ -x $cnf_bin ]; then # take first parameter and remove quotes if there were any so # $ 'foo' # will search for foo - /usr/bin/cnf "${(Q)1}" + $cnf_bin "${(Q)1}" fi } From ab4cd24b351a8d070da4903a928ae6333f84b0a4 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 22:52:51 +1000 Subject: [PATCH 6/9] Added fish support. This mentions that cnf support is already included in fish. It also adds a command_not_found.fish script, but you only need to use that if: command-not-found is not in your path, or not the one you want to use fish has detected another command not found handler, and prefers that Note that overriding the command not found behaviour is only avilable since fish version 1.23.1. --- README.md | 27 ++++++++++++++------------- command_not_found.fish | 7 +++++++ 2 files changed, 21 insertions(+), 13 deletions(-) create mode 100644 command_not_found.fish diff --git a/README.md b/README.md index 0edab3e..7b6e200 100644 --- a/README.md +++ b/README.md @@ -6,17 +6,18 @@ ## **Differences** -| | **`cnf`** | **`scout(cnf)`** | -|------------------------------------|-------------------|-------------------------------| -| **Uses** | `libsolv` | `libsolv` | -| **Written in** | Rust | Shell, two packages in Python | -| **Detect enabled/disabled repos?** | Yes | Yes | -| **Tries to refresh repos** | No | Yes | -| **`bash` integration** | Yes | Yes | -| **`zsh` integration** | Yes | Yes | -| **Disable integration** | Uninstall package | Magic variable | -| **Localization** | Yes (UTF-8 only) | Yes | -| **Package Managers Queried** | Dnf5, Zypper | Zypper | +| | **`cnf`** | **`scout(cnf)`** | +|------------------------------------|---------------------------|-------------------------------| +| **Uses** | `libsolv` | `libsolv` | +| **Written in** | Rust | Shell, two packages in Python | +| **Detect enabled/disabled repos?** | Yes | Yes | +| **Tries to refresh repos** | No | Yes | +| **`bash` integration** | Yes | Yes | +| **`zsh` integration** | Yes | Yes | +| **`fish` integration** | Included with fish 1.23.1 | Included with fish 1.23.1 | +| **Disable integration** | Uninstall package | Magic variable | +| **Localization** | Yes (UTF-8 only) | Yes | +| **Package Managers Queried** | Dnf5, Zypper | Zypper | ## **Build** @@ -58,10 +59,10 @@ To query installed programs in `/usr/bin`: > Absolute path to 'vim' is '/usr/bin/vim'. Please check your $PATH variable to see whether it contains the mentioned path > ``` -## **Integrate with `bash` and `zsh`** +## **Integrate with `bash`, `zsh`, and `fish`** ```.sh -source command_not_found.bash # or command_not_found.zsh +source command_not_found.bash # or command_not_found.zsh, or command_not_found.fish export COMMAND_NOT_FOUND_BIN=./target/debug/cnf cmake ``` diff --git a/command_not_found.fish b/command_not_found.fish new file mode 100644 index 0000000..1cbc47c --- /dev/null +++ b/command_not_found.fish @@ -0,0 +1,7 @@ +function fish_command_not_found + if test -z "$COMMAND_NOT_FOUND_BIN" + set -l COMMAND_NOT_FOUND_BIN /usr/bin/cnf + end + # The 'env' is there in case your fish is older than v3.0.0 + env $COMMAND_NOT_FOUND_BIN $argv[1] +end \ No newline at end of file From f8f5256cbd13b0d4d1e1bc2aff90884e2839a71a Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 19:54:35 +1000 Subject: [PATCH 7/9] Allow manually triggering the ci tests. This enables manually trigger the "PR check" workflow from the github.com web interface, thus allowing people to easilly run the workflow before actually submitting a pull request. --- .github/workflows/pr_check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 6edc9b5..e9b2356 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -6,6 +6,7 @@ on: branches: - "main" pull_request: + workflow_dispatch: jobs: ci: From e79856cc46fc5696649e80b0c8cae016323d86d8 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 19:13:16 +1000 Subject: [PATCH 8/9] Rename docker images with -zypper suffix. The Dockerfile is also renamed to zypper.dockerfile. This is in preparation for having multiple docker images. --- .github/workflows/pr_check.yml | 6 +++--- README.md | 4 ++-- test/root.sh | 2 +- test/{Dockerfile => zypper.dockerfile} | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) rename test/{Dockerfile => zypper.dockerfile} (89%) diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index e9b2356..0894b74 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -52,16 +52,16 @@ jobs: - uses: docker/setup-buildx-action@v2 - - name: Build openSUSE Tumbleweed image + name: Build openSUSE Tumbleweed image with zypper uses: docker/build-push-action@v4 with: cache-from: type=gha cache-to: type=gha,mode=max context: test/ - file: test/Dockerfile + file: test/zypper.dockerfile load: true push: false - tags: local/cnf-ci:latest + tags: local/cnf-ci-zypper:latest - name: Integration test diff --git a/README.md b/README.md index 7b6e200..fdfe44c 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,9 @@ cmake ## **Integration tests** -Integration tests runs inside docker image tagged `local/cnf-ci`. It is built as a part of Github Action and can be built locally as `cd test; docker build -t local/cnf-ci:latest .` +Integration tests runs inside docker image tagged `local/cnf-ci-zypper`. It is built as a part of Github Action and can be built locally as `cd test; docker build -t local/cnf-ci-zypper:latest -f zypper.dockerfile .` -The testing itself is wrapped in [bats](https://github.com/bats-core/bats-core) and in order to make it run, one needs to initialize the git submodules (`git submodule init`). Then tests can be executed using a following command +The testing itself is wrapped in [bats](https://github.com/bats-core/bats-core) and in order to make it run, one needs to initialize the git submodules (`git submodule update --init`). Then tests can be executed using a following command ```.sh ./test/bats/bin/bats ./test/ diff --git a/test/root.sh b/test/root.sh index be93c76..6ab2023 100755 --- a/test/root.sh +++ b/test/root.sh @@ -32,4 +32,4 @@ docker \ --rm \ --user "${USER}" \ ${VOLUMES[*]} \ - local/cnf-ci:latest "${@}" + local/cnf-ci-zypper:latest "${@}" diff --git a/test/Dockerfile b/test/zypper.dockerfile similarity index 89% rename from test/Dockerfile rename to test/zypper.dockerfile index 2c3a504..59c5154 100644 --- a/test/Dockerfile +++ b/test/zypper.dockerfile @@ -1,4 +1,4 @@ -# Tumbleweed based CI image for cnf integration testing +# Tumbleweed based CI image for cnf integration testing with zypper FROM registry.opensuse.org/opensuse/tumbleweed ADD passwd /etc/passwd From ad86bf4007f3129f3da8fdd12618b6ebf8bc3287 Mon Sep 17 00:00:00 2001 From: Isaac Oscar Gariano Date: Fri, 29 Aug 2025 21:48:17 +1000 Subject: [PATCH 9/9] Added CI tests for dnf5. This also builds a new docker image with dnf5 installed, but no zypper. The README is also updated on how to manually build the images and run the tests. The bats submodule is also updated to v1.11.0, as that version introduces a "bats_test_function" to dynamically register test functions. --- .github/workflows/pr_check.yml | 12 ++++- README.md | 48 +++++++++++------ test/bats | 2 +- test/dnf5.dockerfile | 25 +++++++++ test/root.sh | 4 +- test/test.bats | 94 +++++++++++++++++++++------------- 6 files changed, 130 insertions(+), 55 deletions(-) create mode 100644 test/dnf5.dockerfile diff --git a/.github/workflows/pr_check.yml b/.github/workflows/pr_check.yml index 0894b74..6c82be0 100644 --- a/.github/workflows/pr_check.yml +++ b/.github/workflows/pr_check.yml @@ -62,7 +62,17 @@ jobs: load: true push: false tags: local/cnf-ci-zypper:latest - + - + name: Build openSUSE Tumbleweed image with dnf5 + uses: docker/build-push-action@v4 + with: + cache-from: type=gha + cache-to: type=gha,mode=max + context: test/ + file: test/dnf5.dockerfile + load: true + push: false + tags: local/cnf-ci-dnf5:latest - name: Integration test run: ./test/bats/bin/bats ./test diff --git a/README.md b/README.md index fdfe44c..73f7ab3 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,13 @@ cmake ## **Integration tests** -Integration tests runs inside docker image tagged `local/cnf-ci-zypper`. It is built as a part of Github Action and can be built locally as `cd test; docker build -t local/cnf-ci-zypper:latest -f zypper.dockerfile .` +Integration tests runs inside docker images tagged `local/cnf-ci-zypper` and `local/cnf-ci-dnf5`. these are built as a part of Github Action and can be built locally with: + +```.sh +for pm in zypper dnf5; do + docker build -t local/cnf-ci-$pm:latest -f test/$pm.dockerfile test +done +``` The testing itself is wrapped in [bats](https://github.com/bats-core/bats-core) and in order to make it run, one needs to initialize the git submodules (`git submodule update --init`). Then tests can be executed using a following command @@ -86,20 +92,32 @@ The testing itself is wrapped in [bats](https://github.com/bats-core/bats-core) ``` > ```.log > test.bats -> ✓ root: installed /usr/bin/rpm -> > ✓ root: installed /usr/sbin/sysctl -> ✓ root: not installed xnake -> ✓ root: not installed make -> ✓ root: not installed cmake -> ✓ nonroot: not installed cmake -> ✓ nonroot: bash without handler: not installed cmake -> ✓ nonroot: bash handler: not installed cmake -> ✓ nonroot: zsh without handler: not installed cmake -> ✓ nonroot: zsh handler: not installed cmake -> ✓ nonroot: fish handler: not installed cmake -> ✓ issue26: do not list not installable files -> -> 12 tests, 0 failures +> ✓ zypper root: installed /usr/bin/rpm +> ✓ zypper root: installed /usr/sbin/sysctl +> ✓ zypper root: not installed xnake +> ✓ zypper root: not installed make +> ✓ zypper root: not installed cmake +> ✓ zypper nonroot: not installed cmake +> ✓ zypper nonroot: bash without handler: not installed cmake +> ✓ zypper nonroot: bash handler: not installed cmake +> ✓ zypper nonroot: zsh without handler: not installed cmake +> ✓ zypper nonroot: zsh handler: not installed cmake +> ✓ zypper nonroot: fish handler: not installed cmake +> ✓ zypper issue26: do not list not installable files +> ✓ dnf5 root: installed /usr/bin/rpm +> ✓ dnf5 root: installed /usr/sbin/sysctl +> ✓ dnf5 root: not installed xnake +> ✓ dnf5 root: not installed make +> ✓ dnf5 root: not installed cmake +> ✓ dnf5 nonroot: not installed cmake +> ✓ dnf5 nonroot: bash without handler: not installed cmake +> ✓ dnf5 nonroot: bash handler: not installed cmake +> ✓ dnf5 nonroot: zsh without handler: not installed cmake +> ✓ dnf5 nonroot: zsh handler: not installed cmake +> ✓ dnf5 nonroot: fish handler: not installed cmake +> ✓ dnf5 issue26: do not list not installable files +> +> 24 tests, 0 failures > ``` Every test can be executed on a command line. The `root.sh` wrapper mounts the diff --git a/test/bats b/test/bats index 360c1ea..5da6687 160000 --- a/test/bats +++ b/test/bats @@ -1 +1 @@ -Subproject commit 360c1ea5371622132ab669e9cb9687d21298699b +Subproject commit 5da66876b8b619235aee1eb3e54954eaca88059b diff --git a/test/dnf5.dockerfile b/test/dnf5.dockerfile new file mode 100644 index 0000000..cca43b9 --- /dev/null +++ b/test/dnf5.dockerfile @@ -0,0 +1,25 @@ +# Tumbleweed based CI image for cnf integration testing with dnf5 +FROM registry.opensuse.org/opensuse/tumbleweed + +ADD passwd /etc/passwd +ADD group /etc/group +ADD --chown=65532:65532 nonroot /home/nonroot/whoami + +RUN zypper refresh +# dnf5 completely fails to work without the libcurl4 package, yet it isn't transitvely required, recommended, or suggested +RUN zypper --non-interactive install --no-recommends dnf5 libcurl4 rpm-repos-openSUSE-Tumbleweed +RUN dnf5 --assumeyes remove zypper libzypp +# Ensure zypper's cache and settings aren't installed +RUN rm -rf /etc/zypp /var/cache/zypp/ /var/lib/zypp + +# The rest here is the equivalent of Zypper-Dockerfile, but changed to use dnf5 +# Equivelent to zypper addlock +RUN echo excludepkgs=busybox dbus diffutils pam-config perl-base systemd groff man >> /etc/dnf/dnf.conf +# --setopt install_weak_deps=False is equivalent to to zypper --no-recommends +RUN dnf5 --setopt install_weak_deps=False --assumeyes install zsh fish libsolv1 +# Equivalent of zypper addrepo +RUN curl -O https://download.opensuse.org/repositories/GNOME:/Next/openSUSE_Factory/GNOME:Next.repo --output-dir /etc/dnf/repos.d/ +# Equivalent of zypper refresh (note that the default dnf5 settings don't check GPG-keys) +RUN dnf5 makecache + +WORKDIR /src diff --git a/test/root.sh b/test/root.sh index 6ab2023..a554dcb 100755 --- a/test/root.sh +++ b/test/root.sh @@ -26,10 +26,12 @@ VOLUMES[1]="--volume ${CNF_SRC}:/usr/bin/command-not-found:ro" VOLUMES[2]="--volume ${BASH_CNF}:/usr/etc/bash_command_not_found:ro" VOLUMES[3]="--volume ${ZSH_CNF}:/usr/etc/zsh_command_not_found:ro" +PM=$1 +shift docker \ run \ --tty \ --rm \ --user "${USER}" \ ${VOLUMES[*]} \ - local/cnf-ci-zypper:latest "${@}" + local/cnf-ci-$PM:latest "${@}" \ No newline at end of file diff --git a/test/test.bats b/test/test.bats index eba419c..2e6b8c1 100644 --- a/test/test.bats +++ b/test/test.bats @@ -2,7 +2,7 @@ setup() { load 'test_helper/bats-support/load' load 'test_helper/bats-assert/load' - bats_require_minimum_version 1.5.0 + bats_require_minimum_version 1.11.0 # get the containing directory of this file # use $BATS_TEST_FILENAME instead of ${BASH_SOURCE[0]} or $0, @@ -12,65 +12,85 @@ setup() { PATH="$DIR/../test:$PATH" } -@test "root: installed /usr/bin/rpm" { - run root.sh '/usr/bin/cnf' 'rpm' - assert_output --partial "Absolute path to 'rpm' is '/usr/bin/rpm'. Please check your \$PATH variable to see whether it contains the mentioned path." +last_id=0 +# Dynamically make a test: the description is $1, and the body is stdin +function make_test { + name=test_$last_id + last_id=$((last_id + 1)) + eval function "$name" $'{\n' $(cat) $'\n}' + bats_test_function --description "$1" -- "$name" } -@test "root: installed /usr/sbin/sysctl" { - run root.sh '/usr/bin/cnf' 'sysctl' +for PM in zypper dnf5; do + +make_test "$PM root: installed /usr/bin/rpm" <