When depsolve is run it returns a list of packageSpecs and the matching repository config. This repo config is different/distinct from the repository config that is passed into depsolve via the package sets or the repositories that are loaded via the reporegistry and needs to be passed into `manifest.Serialize()`. The key different is that the depsovle `repoConfig` contains the repo.Id that match the packageSpec.RepoId. With those two matching IDs we can use librepo (see osbuild PR#1974) to infere what mirror to use for what package. This commit now passes the correct repoConfig into preManifest.Serialize() now. No test (sorry!) as this is really hard to test in isolation, there is nothing observable today from repoConfig and it is impossible to add a "witness" pipelien that could check that the right argument is passed in `pipeline.serializeStart()`. So until we refactor images you will have to take my word for this (sorry again).
187 lines
5.8 KiB
Go
187 lines
5.8 KiB
Go
package manifestgen
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/osbuild/images/pkg/blueprint"
|
|
"github.com/osbuild/images/pkg/container"
|
|
"github.com/osbuild/images/pkg/distro"
|
|
"github.com/osbuild/images/pkg/dnfjson"
|
|
"github.com/osbuild/images/pkg/ostree"
|
|
"github.com/osbuild/images/pkg/reporegistry"
|
|
"github.com/osbuild/images/pkg/rpmmd"
|
|
"github.com/osbuild/images/pkg/sbom"
|
|
)
|
|
|
|
// XXX: all of the helpers below are duplicated from
|
|
// cmd/build/main.go:depsolve (and probably more places) should go
|
|
// into a common helper in "images" or images should do this on its
|
|
// own
|
|
func defaultDepsolver(cacheDir string, packageSets map[string][]rpmmd.PackageSet, d distro.Distro, arch string) (map[string][]rpmmd.PackageSpec, map[string][]rpmmd.RepoConfig, error) {
|
|
if cacheDir == "" {
|
|
var err error
|
|
cacheDir, err = os.MkdirTemp("", "manifestgen")
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("cannot create temporary directory: %w", err)
|
|
}
|
|
defer os.RemoveAll(cacheDir)
|
|
}
|
|
|
|
solver := dnfjson.NewSolver(d.ModulePlatformID(), d.Releasever(), arch, d.Name(), cacheDir)
|
|
depsolvedSets := make(map[string][]rpmmd.PackageSpec)
|
|
repoSets := make(map[string][]rpmmd.RepoConfig)
|
|
for name, pkgSet := range packageSets {
|
|
res, err := solver.Depsolve(pkgSet, sbom.StandardTypeNone)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("error depsolving: %w", err)
|
|
}
|
|
depsolvedSets[name] = res.Packages
|
|
repoSets[name] = res.Repos
|
|
// the depsolve result also contains SBOM information,
|
|
// it is currently not used here though
|
|
}
|
|
return depsolvedSets, repoSets, nil
|
|
}
|
|
|
|
func resolveContainers(containers []container.SourceSpec, archName string) ([]container.Spec, error) {
|
|
resolver := container.NewResolver(archName)
|
|
|
|
for _, c := range containers {
|
|
resolver.Add(c)
|
|
}
|
|
|
|
return resolver.Finish()
|
|
}
|
|
|
|
func defaultContainerResolver(containerSources map[string][]container.SourceSpec, archName string) (map[string][]container.Spec, error) {
|
|
containerSpecs := make(map[string][]container.Spec, len(containerSources))
|
|
for plName, sourceSpecs := range containerSources {
|
|
specs, err := resolveContainers(sourceSpecs, archName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error container resolving: %w", err)
|
|
}
|
|
containerSpecs[plName] = specs
|
|
}
|
|
return containerSpecs, nil
|
|
}
|
|
|
|
func defaultCommitResolver(commitSources map[string][]ostree.SourceSpec) (map[string][]ostree.CommitSpec, error) {
|
|
commits := make(map[string][]ostree.CommitSpec, len(commitSources))
|
|
for name, commitSources := range commitSources {
|
|
commitSpecs := make([]ostree.CommitSpec, len(commitSources))
|
|
for idx, commitSource := range commitSources {
|
|
var err error
|
|
commitSpecs[idx], err = ostree.Resolve(commitSource)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error ostree commit resolving: %w", err)
|
|
}
|
|
}
|
|
commits[name] = commitSpecs
|
|
}
|
|
return commits, nil
|
|
}
|
|
|
|
type (
|
|
DepsolveFunc func(cacheDir string, packageSets map[string][]rpmmd.PackageSet, d distro.Distro, arch string) (map[string][]rpmmd.PackageSpec, map[string][]rpmmd.RepoConfig, error)
|
|
|
|
ContainerResolverFunc func(containerSources map[string][]container.SourceSpec, archName string) (map[string][]container.Spec, error)
|
|
|
|
CommitResolverFunc func(commitSources map[string][]ostree.SourceSpec) (map[string][]ostree.CommitSpec, error)
|
|
)
|
|
|
|
// Options contains the optional settings for the manifest generation.
|
|
// For unset values defaults will be used.
|
|
type Options struct {
|
|
Cachedir string
|
|
Output io.Writer
|
|
Depsolver DepsolveFunc
|
|
ContainerResolver ContainerResolverFunc
|
|
CommitResolver CommitResolverFunc
|
|
}
|
|
|
|
// Generator can generate an osbuild manifest from a given repository
|
|
// and options.
|
|
type Generator struct {
|
|
cacheDir string
|
|
out io.Writer
|
|
|
|
depsolver DepsolveFunc
|
|
containerResolver ContainerResolverFunc
|
|
commitResolver CommitResolverFunc
|
|
|
|
reporegistry *reporegistry.RepoRegistry
|
|
}
|
|
|
|
// New will create a new manifest generator
|
|
func New(reporegistry *reporegistry.RepoRegistry, opts *Options) (*Generator, error) {
|
|
if opts == nil {
|
|
opts = &Options{}
|
|
}
|
|
mg := &Generator{
|
|
reporegistry: reporegistry,
|
|
|
|
cacheDir: opts.Cachedir,
|
|
out: opts.Output,
|
|
depsolver: opts.Depsolver,
|
|
containerResolver: opts.ContainerResolver,
|
|
commitResolver: opts.CommitResolver,
|
|
}
|
|
if mg.out == nil {
|
|
mg.out = os.Stdout
|
|
}
|
|
if mg.depsolver == nil {
|
|
mg.depsolver = defaultDepsolver
|
|
}
|
|
if mg.containerResolver == nil {
|
|
mg.containerResolver = defaultContainerResolver
|
|
}
|
|
if mg.commitResolver == nil {
|
|
mg.commitResolver = defaultCommitResolver
|
|
}
|
|
|
|
return mg, nil
|
|
}
|
|
|
|
// Generate will generate a new manifest for the given distro/imageType/arch
|
|
// combination.
|
|
func (mg *Generator) Generate(bp *blueprint.Blueprint, dist distro.Distro, imgType distro.ImageType, a distro.Arch, imgOpts *distro.ImageOptions) error {
|
|
if imgOpts == nil {
|
|
imgOpts = &distro.ImageOptions{}
|
|
}
|
|
|
|
repos, err := mg.reporegistry.ReposByImageTypeName(dist.Name(), a.Name(), imgType.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
preManifest, warnings, err := imgType.Manifest(bp, *imgOpts, repos, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(warnings) > 0 {
|
|
// XXX: what can we do here? for things like json output?
|
|
// what are these warnings?
|
|
return fmt.Errorf("warnings during manifest creation: %v", strings.Join(warnings, "\n"))
|
|
}
|
|
packageSpecs, repoConfig, err := mg.depsolver(mg.cacheDir, preManifest.GetPackageSetChains(), dist, a.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
containerSpecs, err := mg.containerResolver(preManifest.GetContainerSourceSpecs(), a.Name())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
commitSpecs, err := mg.commitResolver(preManifest.GetOSTreeSourceSpecs())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mf, err := preManifest.Serialize(packageSpecs, containerSpecs, commitSpecs, repoConfig)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
fmt.Fprintf(mg.out, "%s\n", mf)
|
|
|
|
return nil
|
|
}
|