Creates the 'edge-ami' image type based on edgeRawImage, which generates a raw image (x86_64, aarch64) ready to upload to AWS EC2. This 'edge-ami' image type has Ignition support. Signed-off-by: Irene Diez <idiez@redhat.com>
327 lines
11 KiB
Go
327 lines
11 KiB
Go
package distro_test_common
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
|
"github.com/osbuild/osbuild-composer/internal/common"
|
|
"github.com/osbuild/osbuild-composer/internal/container"
|
|
"github.com/osbuild/osbuild-composer/internal/distro"
|
|
"github.com/osbuild/osbuild-composer/internal/distroregistry"
|
|
"github.com/osbuild/osbuild-composer/internal/dnfjson"
|
|
"github.com/osbuild/osbuild-composer/internal/manifest"
|
|
"github.com/osbuild/osbuild-composer/internal/ostree"
|
|
"github.com/osbuild/osbuild-composer/internal/rhsm/facts"
|
|
"github.com/osbuild/osbuild-composer/internal/rpmmd"
|
|
)
|
|
|
|
const RandomTestSeed = 0
|
|
|
|
func TestDistro_Manifest(t *testing.T, pipelinePath string, prefix string, registry *distroregistry.Registry, depsolvePkgSets bool, dnfCacheDir, dnfJsonPath string) {
|
|
assert := assert.New(t)
|
|
fileNames, err := filepath.Glob(filepath.Join(pipelinePath, prefix))
|
|
assert.NoErrorf(err, "Could not read pipelines directory '%s': %v", pipelinePath, err)
|
|
require.Greaterf(t, len(fileNames), 0, "No pipelines found in %s for %s", pipelinePath, prefix)
|
|
for _, fileName := range fileNames {
|
|
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"`
|
|
PackageSets []string `json:"package-sets,omitempty"`
|
|
}
|
|
type composeRequest struct {
|
|
Distro string `json:"distro"`
|
|
Arch string `json:"arch"`
|
|
ImageType string `json:"image-type"`
|
|
Repositories []repository `json:"repositories"`
|
|
Blueprint *blueprint.Blueprint `json:"blueprint"`
|
|
OSTree *ostree.SourceSpec `json:"ostree"`
|
|
}
|
|
var tt struct {
|
|
ComposeRequest *composeRequest `json:"compose-request"`
|
|
PackageSpecSets map[string][]rpmmd.PackageSpec `json:"rpmmd"`
|
|
Manifest manifest.OSBuildManifest `json:"manifest,omitempty"`
|
|
Containers map[string][]container.Spec `json:"containers,omitempty"`
|
|
}
|
|
file, err := os.ReadFile(fileName)
|
|
assert.NoErrorf(err, "Could not read test-case '%s': %v", fileName, err)
|
|
err = json.Unmarshal([]byte(file), &tt)
|
|
assert.NoErrorf(err, "Could not parse test-case '%s': %v", fileName, err)
|
|
if tt.ComposeRequest == nil || tt.ComposeRequest.Blueprint == nil {
|
|
t.Logf("Skipping '%s'.", fileName)
|
|
continue
|
|
}
|
|
|
|
repos := make([]rpmmd.RepoConfig, len(tt.ComposeRequest.Repositories))
|
|
for i, repo := range tt.ComposeRequest.Repositories {
|
|
var urls []string
|
|
if repo.BaseURL != "" {
|
|
urls = []string{repo.BaseURL}
|
|
}
|
|
var keys []string
|
|
if repo.GPGKey != "" {
|
|
keys = []string{repo.GPGKey}
|
|
}
|
|
repos[i] = rpmmd.RepoConfig{
|
|
Name: fmt.Sprintf("repo-%d", i),
|
|
BaseURLs: urls,
|
|
Metalink: repo.Metalink,
|
|
MirrorList: repo.MirrorList,
|
|
GPGKeys: keys,
|
|
CheckGPG: common.ToPtr(repo.CheckGPG),
|
|
PackageSets: repo.PackageSets,
|
|
}
|
|
}
|
|
t.Run(path.Base(fileName), func(t *testing.T) {
|
|
require.NoError(t, err)
|
|
d := registry.GetDistro(tt.ComposeRequest.Distro)
|
|
if d == nil {
|
|
t.Errorf("unknown distro: %v", tt.ComposeRequest.Distro)
|
|
return
|
|
}
|
|
arch, err := d.GetArch(tt.ComposeRequest.Arch)
|
|
if err != nil {
|
|
t.Errorf("unknown arch: %v", tt.ComposeRequest.Arch)
|
|
return
|
|
}
|
|
imageType, err := arch.GetImageType(tt.ComposeRequest.ImageType)
|
|
if err != nil {
|
|
t.Errorf("unknown image type: %v", tt.ComposeRequest.ImageType)
|
|
return
|
|
}
|
|
|
|
var ostreeOptions *ostree.ImageOptions
|
|
if ref := imageType.OSTreeRef(); ref != "" {
|
|
if tt.ComposeRequest.OSTree != nil {
|
|
ostreeOptions = &ostree.ImageOptions{
|
|
ImageRef: tt.ComposeRequest.OSTree.Ref,
|
|
FetchChecksum: tt.ComposeRequest.OSTree.Parent,
|
|
URL: tt.ComposeRequest.OSTree.URL,
|
|
RHSM: tt.ComposeRequest.OSTree.RHSM,
|
|
}
|
|
}
|
|
}
|
|
if ostreeOptions == nil { // set image type default if not specified in request
|
|
ostreeOptions = &ostree.ImageOptions{
|
|
ImageRef: imageType.OSTreeRef(),
|
|
}
|
|
}
|
|
|
|
options := distro.ImageOptions{
|
|
Size: imageType.Size(0),
|
|
OSTree: ostreeOptions,
|
|
Facts: &facts.ImageOptions{
|
|
APIType: facts.TEST_APITYPE,
|
|
},
|
|
}
|
|
|
|
var imgPackageSpecSets map[string][]rpmmd.PackageSpec
|
|
// depsolve the image's package set to catch changes in the image's default package set.
|
|
// downside is that this takes long time
|
|
if depsolvePkgSets {
|
|
require.NotEmptyf(t, dnfCacheDir, "DNF cache directory path must be provided when chosen to depsolve image package sets")
|
|
require.NotEmptyf(t, dnfJsonPath, "path to 'dnf-json' must be provided when chosen to depsolve image package sets")
|
|
imgPackageSpecSets = getImageTypePkgSpecSets(
|
|
imageType,
|
|
*tt.ComposeRequest.Blueprint,
|
|
options,
|
|
repos,
|
|
dnfCacheDir,
|
|
dnfJsonPath,
|
|
)
|
|
} else {
|
|
imgPackageSpecSets = tt.PackageSpecSets
|
|
}
|
|
|
|
manifest, _, err := imageType.Manifest(tt.ComposeRequest.Blueprint, options, repos, RandomTestSeed)
|
|
if err != nil {
|
|
t.Errorf("distro.Manifest() error = %v", err)
|
|
return
|
|
}
|
|
got, err := manifest.Serialize(imgPackageSpecSets, tt.Containers)
|
|
|
|
if (err == nil && tt.Manifest == nil) || (err != nil && tt.Manifest != nil) {
|
|
t.Errorf("distro.Manifest() error = %v", err)
|
|
return
|
|
}
|
|
if tt.Manifest != nil {
|
|
var expected, actual interface{}
|
|
err = json.Unmarshal(tt.Manifest, &expected)
|
|
require.NoError(t, err)
|
|
err = json.Unmarshal(got, &actual)
|
|
require.NoError(t, err)
|
|
|
|
diff := cmp.Diff(expected, actual)
|
|
require.Emptyf(t, diff, "Distro: %s\nArch: %s\nImage type: %s\nTest case file: %s\n", d.Name(), arch.Name(), imageType.Name(), fileName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func getImageTypePkgSpecSets(imageType distro.ImageType, bp blueprint.Blueprint, options distro.ImageOptions, repos []rpmmd.RepoConfig, cacheDir, dnfJsonPath string) map[string][]rpmmd.PackageSpec {
|
|
manifest, _, err := imageType.Manifest(&bp, options, repos, 0)
|
|
if err != nil {
|
|
panic("Could not generate manifest for package sets: " + err.Error())
|
|
}
|
|
imgPackageSets := manifest.Content.PackageSets
|
|
|
|
solver := dnfjson.NewSolver(imageType.Arch().Distro().ModulePlatformID(),
|
|
imageType.Arch().Distro().Releasever(),
|
|
imageType.Arch().Name(),
|
|
imageType.Arch().Distro().Name(),
|
|
cacheDir)
|
|
solver.SetDNFJSONPath(dnfJsonPath)
|
|
depsolvedSets := make(map[string][]rpmmd.PackageSpec)
|
|
for name, packages := range imgPackageSets {
|
|
res, err := solver.Depsolve(packages)
|
|
if err != nil {
|
|
panic("Could not depsolve: " + err.Error())
|
|
}
|
|
depsolvedSets[name] = res
|
|
}
|
|
|
|
return depsolvedSets
|
|
}
|
|
|
|
func isOSTree(imgType distro.ImageType) bool {
|
|
return imgType.OSTreeRef() != ""
|
|
}
|
|
|
|
var knownKernels = []string{"kernel", "kernel-debug", "kernel-rt"}
|
|
|
|
// Returns the number of known kernels in the package list
|
|
func kernelCount(imgType distro.ImageType, bp blueprint.Blueprint) int {
|
|
ostreeOptions := &ostree.ImageOptions{
|
|
URL: "foo",
|
|
ImageRef: "bar",
|
|
FetchChecksum: "baz",
|
|
}
|
|
manifest, _, err := imgType.Manifest(&bp, distro.ImageOptions{OSTree: ostreeOptions}, nil, 0)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
sets := manifest.Content.PackageSets
|
|
|
|
// Use a map to count unique kernels in a package set. If the same kernel
|
|
// name appears twice, it will only be installed once, so we only count it
|
|
// once.
|
|
kernels := make(map[string]bool)
|
|
for _, name := range []string{
|
|
// payload package set names
|
|
"os", "ostree-tree", "anaconda-tree",
|
|
"packages", "installer",
|
|
} {
|
|
for _, pset := range sets[name] {
|
|
for _, pkg := range pset.Include {
|
|
for _, kernel := range knownKernels {
|
|
if kernel == pkg {
|
|
kernels[kernel] = true
|
|
}
|
|
}
|
|
}
|
|
if len(kernels) > 0 {
|
|
// BUG: some RHEL image types contain both 'packages'
|
|
// and 'installer' even though only 'installer' is used
|
|
// this counts the kernel package twice. None of these
|
|
// sets should appear more than once, so return the count
|
|
// for the first package set that has at least one kernel.
|
|
return len(kernels)
|
|
}
|
|
}
|
|
}
|
|
return len(kernels)
|
|
}
|
|
|
|
func TestDistro_KernelOption(t *testing.T, d distro.Distro) {
|
|
skipList := map[string]bool{
|
|
// Ostree installers and raw images download a payload to embed or
|
|
// deploy. The kernel is part of the payload so it doesn't appear in
|
|
// the image type's package lists.
|
|
"iot-installer": true,
|
|
"edge-installer": true,
|
|
"edge-simplified-installer": true,
|
|
"iot-raw-image": true,
|
|
"edge-raw-image": true,
|
|
"edge-ami": true,
|
|
|
|
// the tar image type is a minimal image type which is not expected to
|
|
// be usable without a blueprint (see commit 83a63aaf172f556f6176e6099ffaa2b5357b58f5).
|
|
"tar": true,
|
|
|
|
// containers don't have kernels
|
|
"container": true,
|
|
|
|
// image installer on Fedora doesn't support kernel customizations
|
|
// on RHEL we support kernel name
|
|
// TODO: Remove when we unify the allowed options
|
|
"image-installer": true,
|
|
}
|
|
|
|
{ // empty blueprint: all image types should just have the default kernel
|
|
for _, archName := range d.ListArches() {
|
|
arch, err := d.GetArch(archName)
|
|
assert.NoError(t, err)
|
|
for _, typeName := range arch.ListImageTypes() {
|
|
if true {
|
|
break
|
|
}
|
|
if skipList[typeName] {
|
|
continue
|
|
}
|
|
imgType, err := arch.GetImageType(typeName)
|
|
assert.NoError(t, err)
|
|
nk := kernelCount(imgType, blueprint.Blueprint{})
|
|
|
|
if nk != 1 {
|
|
assert.Fail(t, fmt.Sprintf("%s Kernel count", d.Name()),
|
|
"Image type %s (arch %s) specifies %d Kernel packages", typeName, archName, nk)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
{ // kernel in blueprint: the specified kernel replaces the default
|
|
for _, kernelName := range []string{"kernel", "kernel-debug"} {
|
|
bp := blueprint.Blueprint{
|
|
Customizations: &blueprint.Customizations{
|
|
Kernel: &blueprint.KernelCustomization{
|
|
Name: kernelName,
|
|
},
|
|
},
|
|
}
|
|
for _, archName := range d.ListArches() {
|
|
arch, err := d.GetArch(archName)
|
|
assert.NoError(t, err)
|
|
for _, typeName := range arch.ListImageTypes() {
|
|
if typeName != "image-installer" {
|
|
continue
|
|
}
|
|
if skipList[typeName] {
|
|
continue
|
|
}
|
|
imgType, err := arch.GetImageType(typeName)
|
|
assert.NoError(t, err)
|
|
nk := kernelCount(imgType, bp)
|
|
|
|
// ostree image types should have only one kernel
|
|
// other image types should have at least 1
|
|
if nk < 1 || (nk != 1 && isOSTree(imgType)) {
|
|
assert.Fail(t, fmt.Sprintf("%s Kernel count", d.Name()),
|
|
"Image type %s (arch %s) specifies %d Kernel packages", typeName, archName, nk)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|