osbuild/images#751 wrapped the errors in the images/dnfjson package to provide more details, the depsolve job should take this into account to map the dnfjson error to the correct worker client error. This caused user input errors errors to be misclassified as internal errors, triggering depsolve job failure alerts.
159 lines
5 KiB
Go
159 lines
5 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/osbuild/images/pkg/dnfjson"
|
|
"github.com/osbuild/images/pkg/rpmmd"
|
|
"github.com/osbuild/osbuild-composer/internal/worker"
|
|
"github.com/osbuild/osbuild-composer/internal/worker/clienterrors"
|
|
)
|
|
|
|
// Used by both depsolve and osbuild jobs
|
|
type RepositoryMTLSConfig struct {
|
|
BaseURL *url.URL
|
|
CA string
|
|
MTLSClientKey string
|
|
MTLSClientCert string
|
|
Proxy *url.URL
|
|
}
|
|
|
|
func (rmc *RepositoryMTLSConfig) CompareBaseURL(baseURLStr string) (bool, error) {
|
|
baseURL, err := url.Parse(baseURLStr)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if baseURL.Scheme != rmc.BaseURL.Scheme {
|
|
return false, nil
|
|
}
|
|
if baseURL.Host != rmc.BaseURL.Host {
|
|
return false, nil
|
|
}
|
|
if !strings.HasPrefix(baseURL.Path, rmc.BaseURL.Path) {
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
type DepsolveJobImpl struct {
|
|
Solver *dnfjson.BaseSolver
|
|
RepositoryMTLSConfig *RepositoryMTLSConfig
|
|
}
|
|
|
|
// depsolve each package set in the pacakgeSets map. The repositories defined
|
|
// in repos are used for all package sets, whereas the repositories in
|
|
// packageSetsRepos are only used for the package set with the same name
|
|
// (matching map keys).
|
|
func (impl *DepsolveJobImpl) depsolve(packageSets map[string][]rpmmd.PackageSet, modulePlatformID, arch, releasever string) (map[string][]rpmmd.PackageSpec, map[string][]rpmmd.RepoConfig, error) {
|
|
solver := impl.Solver.NewWithConfig(modulePlatformID, releasever, arch, "")
|
|
if impl.RepositoryMTLSConfig != nil && impl.RepositoryMTLSConfig.Proxy != nil {
|
|
err := solver.SetProxy(impl.RepositoryMTLSConfig.Proxy.String())
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
}
|
|
|
|
depsolvedSets := make(map[string][]rpmmd.PackageSpec)
|
|
repoConfigs := make(map[string][]rpmmd.RepoConfig)
|
|
for name, pkgSet := range packageSets {
|
|
res, repos, err := solver.Depsolve(pkgSet)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
depsolvedSets[name] = res
|
|
repoConfigs[name] = repos
|
|
}
|
|
|
|
return depsolvedSets, repoConfigs, nil
|
|
}
|
|
|
|
func workerClientErrorFrom(err error, logWithId *logrus.Entry) *clienterrors.Error {
|
|
if err == nil {
|
|
logWithId.Errorf("workerClientErrorFrom expected an error to be processed. Not nil")
|
|
}
|
|
|
|
var dnfjsonErr dnfjson.Error
|
|
if errors.As(err, &dnfjsonErr) {
|
|
// Error originates from dnf-json
|
|
reason := fmt.Sprintf("DNF error occurred: %s", dnfjsonErr.Kind)
|
|
details := dnfjsonErr.Reason
|
|
switch dnfjsonErr.Kind {
|
|
case "DepsolveError":
|
|
return clienterrors.New(clienterrors.ErrorDNFDepsolveError, reason, details)
|
|
case "MarkingErrors":
|
|
return clienterrors.New(clienterrors.ErrorDNFMarkingErrors, reason, details)
|
|
case "RepoError":
|
|
return clienterrors.New(clienterrors.ErrorDNFRepoError, reason, details)
|
|
default:
|
|
logWithId.Errorf("Unhandled dnf-json error in depsolve job: %v", err)
|
|
// This still has the kind/reason format but a kind that's returned
|
|
// by dnf-json and not explicitly handled here.
|
|
return clienterrors.New(clienterrors.ErrorDNFOtherError, reason, details)
|
|
}
|
|
} else {
|
|
reason := "rpmmd error in depsolve job"
|
|
details := fmt.Sprintf("%v", err)
|
|
// Error originates from internal/rpmmd, not from dnf-json
|
|
//
|
|
// XXX: it seems slightly dangerous to assume that any
|
|
// "error" we get there is coming from rpmmd, can we
|
|
// generate a more typed error from dnfjson here for
|
|
// rpmmd errors?
|
|
return clienterrors.New(clienterrors.ErrorRPMMDError, reason, details)
|
|
}
|
|
}
|
|
|
|
func (impl *DepsolveJobImpl) Run(job worker.Job) error {
|
|
logWithId := logrus.WithField("jobId", job.Id())
|
|
var args worker.DepsolveJob
|
|
err := job.Args(&args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var result worker.DepsolveJobResult
|
|
|
|
if impl.RepositoryMTLSConfig != nil {
|
|
for pkgsetsi, pkgsets := range args.PackageSets {
|
|
for pkgseti, pkgset := range pkgsets {
|
|
for repoi, repo := range pkgset.Repositories {
|
|
for _, baseurlstr := range repo.BaseURLs {
|
|
match, err := impl.RepositoryMTLSConfig.CompareBaseURL(baseurlstr)
|
|
if err != nil {
|
|
result.JobError = clienterrors.New(clienterrors.ErrorInvalidRepositoryURL, "Repository URL is malformed", err.Error())
|
|
return err
|
|
}
|
|
if match {
|
|
args.PackageSets[pkgsetsi][pkgseti].Repositories[repoi].SSLCACert = impl.RepositoryMTLSConfig.CA
|
|
args.PackageSets[pkgsetsi][pkgseti].Repositories[repoi].SSLClientKey = impl.RepositoryMTLSConfig.MTLSClientKey
|
|
args.PackageSets[pkgsetsi][pkgseti].Repositories[repoi].SSLClientCert = impl.RepositoryMTLSConfig.MTLSClientCert
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
result.PackageSpecs, result.RepoConfigs, err = impl.depsolve(args.PackageSets, args.ModulePlatformID, args.Arch, args.Releasever)
|
|
if err != nil {
|
|
result.JobError = workerClientErrorFrom(err, logWithId)
|
|
}
|
|
if err := impl.Solver.CleanCache(); err != nil {
|
|
// log and ignore
|
|
logWithId.Errorf("Error during rpm repo cache cleanup: %s", err.Error())
|
|
}
|
|
|
|
err = job.Update(&result)
|
|
if err != nil {
|
|
return fmt.Errorf("Error reporting job result: %v", err)
|
|
}
|
|
|
|
return nil
|
|
}
|