dnf-json: make independent from the host

We must avoid depending on the host's state in any way. This achieves
isolation in the following ways:
 - rather than the default config file /dev/null is used
 - rather than sharing the host persistent state dir a temporary one
   is used and thrown away for each call
 - the module_platform_id is set explicitly per supported distro, rather
   than taken from /etc/os-release.

Optionally, the cache directory can be configured, as we may want to keep
this separate from the host, if for no other reason than accounting.
However, the cache appears to be well-behaved, so we can keep sharing
it between calls (or even with the host). This speeds up things
considerably, so this is definitely what we want.

Signed-off-by: Tom Gundersen <teg@jklm.no>
This commit is contained in:
Tom Gundersen 2020-02-04 15:06:45 +01:00 committed by msehnout
parent b6d9268810
commit cdd1912e78
5 changed files with 81 additions and 68 deletions

View file

@ -81,7 +81,7 @@ func main() {
}
rpmmd := rpmmd.NewRPMMD()
_, checksums, err := rpmmd.Depsolve(packages, nil, d.Repositories(archArg), true)
_, checksums, err := rpmmd.Depsolve(packages, nil, d.Repositories(archArg), d.ModulePlatformID(), true)
if err != nil {
panic(err.Error())
}

107
dnf-json
View file

@ -7,6 +7,7 @@ import hawkey
import json
import shutil
import sys
import tempfile
DNF_ERROR_EXIT_CODE = 10
@ -37,8 +38,13 @@ def dnfrepo(desc, parent_conf=None):
return repo
def create_base(repos, clean=False):
def create_base(repos, module_platform_id, persistdir, cachedir, clean=False):
base = dnf.Base()
base.conf.module_platform_id = module_platform_id
base.conf.config_file_path = "/dev/null"
base.conf.persistdir = persistdir
if cachedir:
base.conf.cachedir = cachedir
if clean:
shutil.rmtree(base.conf.cachedir, ignore_errors=True)
@ -81,55 +87,60 @@ def repo_checksums(base):
call = json.load(sys.stdin)
command = call["command"]
arguments = call.get("arguments", {})
arguments = call["arguments"]
repos = arguments.get("repos", {})
clean = arguments.get("clean", False)
cachedir = arguments.get("cachedir", None)
module_platform_id = arguments["module_platform_id"]
if command == "dump":
base = create_base(arguments.get("repos", {}), arguments.get("clean", False))
packages = []
for package in base.sack.query().available():
packages.append({
"name": package.name,
"summary": package.summary,
"description": package.description,
"url": package.url,
"epoch": package.epoch,
"version": package.version,
"release": package.release,
"arch": package.arch,
"buildtime": timestamp_to_rfc3339(package.buildtime),
"license": package.license
})
json.dump({
"checksums": repo_checksums(base),
"packages": packages
}, sys.stdout)
with tempfile.TemporaryDirectory() as persistdir:
base = create_base(repos, module_platform_id, persistdir, cachedir, clean)
elif command == "depsolve":
base = create_base(arguments.get("repos", {}), arguments.get("clean", False))
errors = []
if command == "dump":
packages = []
for package in base.sack.query().available():
packages.append({
"name": package.name,
"summary": package.summary,
"description": package.description,
"url": package.url,
"epoch": package.epoch,
"version": package.version,
"release": package.release,
"arch": package.arch,
"buildtime": timestamp_to_rfc3339(package.buildtime),
"license": package.license
})
json.dump({
"checksums": repo_checksums(base),
"packages": packages
}, sys.stdout)
try:
base.install_specs(arguments["package-specs"], exclude=arguments.get("exclude-specs", []))
except dnf.exceptions.MarkingErrors as e:
exit_with_dnf_error("MarkingErrors", f"Error occurred when marking packages for installation: {e}")
elif command == "depsolve":
errors = []
try:
base.resolve()
except dnf.exceptions.DepsolveError as e:
exit_with_dnf_error("DepsolveError", f"There was a problem depsolving {arguments['package-specs']}: {e}")
try:
base.install_specs(arguments["package-specs"], exclude=arguments.get("exclude-specs", []))
except dnf.exceptions.MarkingErrors as e:
exit_with_dnf_error("MarkingErrors", f"Error occurred when marking packages for installation: {e}")
dependencies = []
for package in base.transaction.install_set:
dependencies.append({
"name": package.name,
"epoch": package.epoch,
"version": package.version,
"release": package.release,
"arch": package.arch,
"remote_location": package.remote_location(),
"checksum": f"{hawkey.chksum_name(package.chksum[0])}:{package.chksum[1].hex()}",
})
json.dump({
"checksums": repo_checksums(base),
"dependencies": dependencies
}, sys.stdout)
try:
base.resolve()
except dnf.exceptions.DepsolveError as e:
exit_with_dnf_error("DepsolveError", f"There was a problem depsolving {arguments['package-specs']}: {e}")
dependencies = []
for package in base.transaction.install_set:
dependencies.append({
"name": package.name,
"epoch": package.epoch,
"version": package.version,
"release": package.release,
"arch": package.arch,
"remote_location": package.remote_location(),
"checksum": f"{hawkey.chksum_name(package.chksum[0])}:{package.chksum[1].hex()}",
})
json.dump({
"checksums": repo_checksums(base),
"dependencies": dependencies
}, sys.stdout)

View file

@ -30,10 +30,10 @@ func NewRPMMDMock(fixture Fixture) rpmmd.RPMMD {
return &rpmmdMock{Fixture: fixture}
}
func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig) (rpmmd.PackageList, map[string]string, error) {
func (r *rpmmdMock) FetchPackageList(repos []rpmmd.RepoConfig, modulePlatformID string) (rpmmd.PackageList, map[string]string, error) {
return r.Fixture.fetchPackageList.ret, r.Fixture.fetchPackageList.checksums, r.Fixture.fetchPackageList.err
}
func (r *rpmmdMock) Depsolve(specs, excludeSpecs []string, repos []rpmmd.RepoConfig, clean bool) ([]rpmmd.PackageSpec, map[string]string, error) {
func (r *rpmmdMock) Depsolve(specs, excludeSpecs []string, repos []rpmmd.RepoConfig, modulePlatformID string, clean bool) ([]rpmmd.PackageSpec, map[string]string, error) {
return r.Fixture.depsolve.ret, r.Fixture.fetchPackageList.checksums, r.Fixture.depsolve.err
}

View file

@ -95,8 +95,8 @@ type PackageInfo struct {
}
type RPMMD interface {
FetchPackageList(repos []RepoConfig) (PackageList, map[string]string, error)
Depsolve(specs, excludeSpecs []string, repos []RepoConfig, clean bool) ([]PackageSpec, map[string]string, error)
FetchPackageList(repos []RepoConfig, modulePlatformID string) (PackageList, map[string]string, error)
Depsolve(specs, excludeSpecs []string, repos []RepoConfig, modulePlatformID string, clean bool) ([]PackageSpec, map[string]string, error)
}
type DNFError struct {
@ -199,10 +199,11 @@ func NewRPMMD() RPMMD {
return &rpmmdImpl{}
}
func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, map[string]string, error) {
func (*rpmmdImpl) FetchPackageList(repos []RepoConfig, modulePlatformID string) (PackageList, map[string]string, error) {
var arguments = struct {
Repos []RepoConfig `json:"repos"`
}{repos}
Repos []RepoConfig `json:"repos"`
ModulePlatformID string `json:"module_platform_id"`
}{repos, modulePlatformID}
var reply struct {
Checksums map[string]string `json:"checksums"`
Packages PackageList `json:"packages"`
@ -214,13 +215,14 @@ func (*rpmmdImpl) FetchPackageList(repos []RepoConfig) (PackageList, map[string]
return reply.Packages, reply.Checksums, err
}
func (*rpmmdImpl) Depsolve(specs, excludeSpecs []string, repos []RepoConfig, clean bool) ([]PackageSpec, map[string]string, error) {
func (*rpmmdImpl) Depsolve(specs, excludeSpecs []string, repos []RepoConfig, modulePlatformID string, clean bool) ([]PackageSpec, map[string]string, error) {
var arguments = struct {
PackageSpecs []string `json:"package-specs"`
ExcludSpecs []string `json:"exclude-specs"`
Repos []RepoConfig `json:"repos"`
Clean bool `json:"clean,omitempty"`
}{specs, excludeSpecs, repos, clean}
PackageSpecs []string `json:"package-specs"`
ExcludSpecs []string `json:"exclude-specs"`
Repos []RepoConfig `json:"repos"`
ModulePlatformID string `json:"module_platform_id"`
Clean bool `json:"clean,omitempty"`
}{specs, excludeSpecs, repos, modulePlatformID, clean}
var reply struct {
Checksums map[string]string `json:"checksums"`
Dependencies []PackageSpec `json:"dependencies"`
@ -281,7 +283,7 @@ func (packages PackageList) ToPackageInfos() []PackageInfo {
return results
}
func (pkg *PackageInfo) FillDependencies(rpmmd RPMMD, repos []RepoConfig) (err error) {
pkg.Dependencies, _, err = rpmmd.Depsolve([]string{pkg.Name}, nil, repos, false)
func (pkg *PackageInfo) FillDependencies(rpmmd RPMMD, repos []RepoConfig, modulePlatformID string) (err error) {
pkg.Dependencies, _, err = rpmmd.Depsolve([]string{pkg.Name}, nil, repos, modulePlatformID, false)
return
}

View file

@ -615,7 +615,7 @@ func (api *API) modulesInfoHandler(writer http.ResponseWriter, request *http.Req
if modulesRequested {
for i, _ := range packageInfos {
err := packageInfos[i].FillDependencies(api.rpmmd, api.distro.Repositories(api.arch))
err := packageInfos[i].FillDependencies(api.rpmmd, api.distro.Repositories(api.arch), api.distro.ModulePlatformID())
if err != nil {
errors := responseError{
ID: errorId,
@ -645,7 +645,7 @@ func (api *API) projectsDepsolveHandler(writer http.ResponseWriter, request *htt
names := strings.Split(params.ByName("projects"), ",")
packages, _, err := api.rpmmd.Depsolve(names, nil, api.distro.Repositories(api.arch), false)
packages, _, err := api.rpmmd.Depsolve(names, nil, api.distro.Repositories(api.arch), api.distro.ModulePlatformID(), false)
if err != nil {
errors := responseError{
@ -1792,7 +1792,7 @@ func (api *API) fetchPackageList() (rpmmd.PackageList, error) {
repos = append(repos, source.RepoConfig())
}
packages, _, err := api.rpmmd.FetchPackageList(repos)
packages, _, err := api.rpmmd.FetchPackageList(repos, api.distro.ModulePlatformID())
return packages, err
}
@ -1817,7 +1817,7 @@ func (api *API) depsolveBlueprint(bp *blueprint.Blueprint, clean bool) ([]rpmmd.
repos = append(repos, source.RepoConfig())
}
return api.rpmmd.Depsolve(specs, nil, repos, clean)
return api.rpmmd.Depsolve(specs, nil, repos, api.distro.ModulePlatformID(), clean)
}
func (api *API) uploadsScheduleHandler(writer http.ResponseWriter, request *http.Request, params httprouter.Params) {