debian-forge-composer/cmd/osbuild-pipeline/main.go
Ondřej Budai 973639d372 distro/rhel84: use a random uuid for XFS partition
Imagine this situation: You have a RHEL system booted from an image produced
by osbuild-composer. On this system, you want to use osbuild-composer to
create another image of RHEL.

However, there's currently something funny with partitions:

All RHEL images built by osbuild-composer contain a root xfs partition. The
interesting bit is that they all share the same xfs partition UUID. This might
sound like a good thing for reproducibility but it has a quirk.

The issue appears when osbuild runs the qemu assembler: it needs to mount all
partitions of the future image to copy the OS tree into it.

Imagine that osbuild-composer is running on a system booted from an imaged
produced by osbuild-composer. This means that its root xfs partition has this
uuid:

efe8afea-c0a8-45dc-8e6e-499279f6fa5d

When osbuild-composer builds an image on this system, it runs osbuild that
runs the qemu assembler at some point. As I said previously, it will mount
all partitions of the future image. That means that it will also try to
mount the root xfs partition with this uuid:

efe8afea-c0a8-45dc-8e6e-499279f6fa5d

Do you remember this one? Yeah, it's the same one as before. However, the xfs
kernel driver doesn't like that. It contains a global table[1] of all xfs
partitions that forbids to mount 2 xfs partitions with the same uuid.

I mean... uuids are meant to be unique, right?

This commit changes the way we build RHEL 8.4 images: Each one now has a
unique uuid. It's now literally a unique universally unique identifier. haha

[1]: a349e4c659/fs/xfs/xfs_mount.c (L51)
2020-12-15 16:43:39 +01:00

169 lines
4.7 KiB
Go

package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"github.com/osbuild/osbuild-composer/internal/distro/fedora32"
"github.com/osbuild/osbuild-composer/internal/distro/fedora33"
"github.com/osbuild/osbuild-composer/internal/distro/rhel8"
"github.com/osbuild/osbuild-composer/internal/distro/rhel84"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
type repository struct {
BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"`
GPGKey string `json:"gpgkey,omitempty"`
CheckGPG bool `json:"check_gpg,omitempty"`
}
type composeRequest struct {
Distro string `json:"distro"`
Arch string `json:"arch"`
ImageType string `json:"image-type"`
Blueprint blueprint.Blueprint `json:"blueprint"`
Repositories []repository `json:"repositories"`
}
type rpmMD struct {
BuildPackages []rpmmd.PackageSpec `json:"build-packages"`
Packages []rpmmd.PackageSpec `json:"packages"`
Checksums map[string]string `json:"checksums"`
}
func main() {
var rpmmdArg bool
flag.BoolVar(&rpmmdArg, "rpmmd", false, "output rpmmd struct instead of pipeline manifest")
var seedArg int64
flag.Int64Var(&seedArg, "seed", 0, "seed for generating manifests (default: 0)")
flag.Parse()
// Path to composeRequet or '-' for stdin
composeRequestArg := flag.Arg(0)
composeRequest := &composeRequest{}
if composeRequestArg != "" {
var reader io.Reader
if composeRequestArg == "-" {
reader = os.Stdin
} else {
var err error
reader, err = os.Open(composeRequestArg)
if err != nil {
panic("Could not open compose request: " + err.Error())
}
}
file, err := ioutil.ReadAll(reader)
if err != nil {
panic("Could not read compose request: " + err.Error())
}
err = json.Unmarshal(file, &composeRequest)
if err != nil {
panic("Could not parse blueprint: " + err.Error())
}
}
distros, err := distro.NewRegistry(fedora32.New(), fedora33.New(), rhel8.New(), rhel84.New())
if err != nil {
panic(err)
}
d := distros.GetDistro(composeRequest.Distro)
if d == nil {
_, _ = fmt.Fprintf(os.Stderr, "The provided distribution '%s' is not supported. Use one of these:\n", composeRequest.Distro)
for _, d := range distros.List() {
_, _ = fmt.Fprintln(os.Stderr, " *", d)
}
return
}
arch, err := d.GetArch(composeRequest.Arch)
if err != nil {
fmt.Fprintf(os.Stderr, "The provided architecture '%s' is not supported by %s. Use one of these:\n", composeRequest.Arch, d.Name())
for _, a := range d.ListArches() {
_, _ = fmt.Fprintln(os.Stderr, " *", a)
}
return
}
imageType, err := arch.GetImageType(composeRequest.ImageType)
if err != nil {
fmt.Fprintf(os.Stderr, "The provided image type '%s' is not supported by %s for %s. Use one of these:\n", composeRequest.ImageType, d.Name(), arch.Name())
for _, t := range arch.ListImageTypes() {
_, _ = fmt.Fprintln(os.Stderr, " *", t)
}
return
}
repos := make([]rpmmd.RepoConfig, len(composeRequest.Repositories))
for i, repo := range composeRequest.Repositories {
repos[i] = rpmmd.RepoConfig{
Name: fmt.Sprintf("repo-%d", i),
BaseURL: repo.BaseURL,
Metalink: repo.Metalink,
MirrorList: repo.MirrorList,
GPGKey: repo.GPGKey,
CheckGPG: repo.CheckGPG,
}
}
packages, excludePkgs := imageType.Packages(composeRequest.Blueprint)
home, err := os.UserHomeDir()
if err != nil {
panic("os.UserHomeDir(): " + err.Error())
}
rpmmd := rpmmd.NewRPMMD(path.Join(home, ".cache/osbuild-composer/rpmmd"), "/usr/libexec/osbuild-composer/dnf-json")
packageSpecs, checksums, err := rpmmd.Depsolve(packages, excludePkgs, repos, d.ModulePlatformID(), arch.Name())
if err != nil {
panic("Could not depsolve: " + err.Error())
}
buildPkgs := imageType.BuildPackages()
buildPackageSpecs, _, err := rpmmd.Depsolve(buildPkgs, nil, repos, d.ModulePlatformID(), arch.Name())
if err != nil {
panic("Could not depsolve build packages: " + err.Error())
}
var bytes []byte
if rpmmdArg {
rpmMDInfo := rpmMD{
BuildPackages: buildPackageSpecs,
Packages: packageSpecs,
Checksums: checksums,
}
bytes, err = json.Marshal(rpmMDInfo)
if err != nil {
panic(err)
}
} else {
manifest, err := imageType.Manifest(composeRequest.Blueprint.Customizations,
distro.ImageOptions{
Size: imageType.Size(0),
},
repos,
packageSpecs,
buildPackageSpecs,
seedArg)
if err != nil {
panic(err.Error())
}
bytes, err = json.Marshal(manifest)
if err != nil {
panic(err)
}
}
os.Stdout.Write(bytes)
}