Skip to content

Commit

Permalink
dnfjson: support redirecting dnfjsons stderr
Browse files Browse the repository at this point in the history
This commit tweaks the dnfjson.Solver to also have a `Stderr`
attribute. This is a minimal change to prevent `image-builder-cli`
from showing:
```console
image-builder build qcow2 --distro centos-9 --progress=verbose
Manifest generation step
Building manifest for qcow2-centos-9
No match for group package "iwl5150-firmware"
No match for group package "iwl6050-firmware"
No match for group package "firewalld"
No match for group package "iwl7260-firmware"
No match for group package "dracut-config-rescue"
No match for group package "iwl105-firmware"
No match for group package "iwl2000-firmware"
No match for group package "iwl100-firmware"
No match for group package "iwl6000g2a-firmware"
No match for group package "iwl1000-firmware"
No match for group package "iwl3160-firmware"
No match for group package "iwl135-firmware"
No match for group package "iwl2030-firmware"
No match for group package "iwl5000-firmware"
...
```
which seems to be resulting from the "exclude" directive of the
transaction. When qcow2 in centos-9 is build it contains the following:
```go
	ps := rpmmd.PackageSet{
		Include: []string{
			"@core",
			...
		},
		Exclude: []string{
			...
			"firewalld",
			"iwl7260-firmware",
			...
		},
```
and each package that is both in @core and in the exclude seems
to trigger a message like the above, e.g.:
```
No match for group package "firewalld"
```
This is just confusing for our users so image-builder-cli will
just discard this stderr output from dnfjson.

I'm not sure if there is a better way to handle this though, ideas
(very) welcome.
  • Loading branch information
mvo5 authored and achilleas-k committed Jan 30, 2025
1 parent 41d97aa commit 2621d49
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 7 deletions.
24 changes: 18 additions & 6 deletions pkg/dnfjson/dnfjson.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"os/exec"
Expand Down Expand Up @@ -154,6 +155,14 @@ type Solver struct {
proxy string

subscriptions *rhsm.Subscriptions

// Stderr is the stderr output from dnfjson, if unset os.Stderr
// will be used.
//
// XXX: ideally this would not be public but just passed via
// NewSolver() but it already has 5 args so ideally we would
// add a SolverOptions struct here with "CacheDir" and "Stderr"?
Stderr io.Writer
}

// DepsolveResult contains the results of a depsolve operation.
Expand Down Expand Up @@ -212,7 +221,7 @@ func (s *Solver) Depsolve(pkgSets []rpmmd.PackageSet, sbomType sbom.StandardType
s.cache.locker.RLock()
defer s.cache.locker.RUnlock()

output, err := run(s.dnfJsonCmd, req)
output, err := run(s.dnfJsonCmd, req, s.Stderr)
if err != nil {
return nil, fmt.Errorf("running osbuild-depsolve-dnf failed:\n%w", err)
}
Expand Down Expand Up @@ -266,7 +275,7 @@ func (s *Solver) FetchMetadata(repos []rpmmd.RepoConfig) (rpmmd.PackageList, err
return pkgs, nil
}

result, err := run(s.dnfJsonCmd, req)
result, err := run(s.dnfJsonCmd, req, s.Stderr)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -312,7 +321,7 @@ func (s *Solver) SearchMetadata(repos []rpmmd.RepoConfig, packages []string) (rp
return pkgs, nil
}

result, err := run(s.dnfJsonCmd, req)
result, err := run(s.dnfJsonCmd, req, s.Stderr)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -835,7 +844,7 @@ func ParseError(data []byte) Error {
return e
}

func run(dnfJsonCmd []string, req *Request) ([]byte, error) {
func run(dnfJsonCmd []string, req *Request, stderr io.Writer) ([]byte, error) {
if len(dnfJsonCmd) == 0 {
dnfJsonCmd = []string{findDepsolveDnf()}
}
Expand All @@ -853,7 +862,11 @@ func run(dnfJsonCmd []string, req *Request) ([]byte, error) {
return nil, fmt.Errorf("creating stdin pipe for %s failed: %w", ex, err)
}

cmd.Stderr = os.Stderr
if stderr != nil {
cmd.Stderr = stderr
} else {
cmd.Stderr = os.Stderr
}
stdout := new(bytes.Buffer)
cmd.Stdout = stdout

Expand All @@ -873,6 +886,5 @@ func run(dnfJsonCmd []string, req *Request) ([]byte, error) {
if runError, ok := err.(*exec.ExitError); ok && runError.ExitCode() != 0 {
return nil, parseError(output, req.Arguments.Repos)
}

return output, nil
}
6 changes: 5 additions & 1 deletion pkg/dnfjson/dnfjson_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,7 +858,7 @@ exit 1
err := os.WriteFile(fakeDnfJsonPath, []byte(fakeDnfJsonNoOutput), 0o755)
assert.NoError(t, err)

_, err = run([]string{fakeDnfJsonPath}, &Request{})
_, err = run([]string{fakeDnfJsonPath}, &Request{}, nil)
assert.EqualError(t, err, `DNF error occurred: InternalError: dnf-json output was empty`)
}

Expand All @@ -867,16 +867,20 @@ func TestSolverRunWithSolverNoError(t *testing.T) {
fakeSolver := `#!/bin/sh -e
cat - > "$0".stdin
echo '{"solver": "zypper"}'
>&2 echo "output-on-stderr"
`
fakeSolverPath := filepath.Join(tmpdir, "fake-solver")
err := os.WriteFile(fakeSolverPath, []byte(fakeSolver), 0755) //nolint:gosec
assert.NoError(t, err)

var capturedStderr bytes.Buffer
solver := NewSolver("platform:f38", "38", "x86_64", "fedora-38", "/tmp/cache")
solver.Stderr = &capturedStderr
solver.dnfJsonCmd = []string{fakeSolverPath}
res, err := solver.Depsolve(nil, sbom.StandardTypeNone)
assert.NoError(t, err)
assert.NotNil(t, res)
assert.Equal(t, "output-on-stderr\n", capturedStderr.String())

// prerequisite check, i.e. ensure our fake was called in the right way
stdin, err := os.ReadFile(fakeSolverPath + ".stdin")
Expand Down

0 comments on commit 2621d49

Please sign in to comment.