From 78e983b69a46f9936c30c16e2218b8ff487922cf Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Mon, 4 Aug 2025 16:39:26 -0700 Subject: [PATCH] cloudapi: Add support for depsolving a blueprint + image type This uses the blueprint and the image type's manifest with a minimal imageRequest to get the package sets to use for depsolving, instead of just using the packages in the blueprint. This does not work with iot image types that require an ostree url. Depsolving iot commits doesn't make sense anyway, since the blueprint packages have no effect on it. Includes simple tests for depsolving with image type, and returning an error if the image type isn't recognized. Related: RHEL-60125 --- internal/cloudapi/v2/depsolve.go | 50 ++++++++++++++++--- internal/cloudapi/v2/v2_test.go | 85 ++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 23 deletions(-) diff --git a/internal/cloudapi/v2/depsolve.go b/internal/cloudapi/v2/depsolve.go index 1fbc62e21..cb5acba87 100644 --- a/internal/cloudapi/v2/depsolve.go +++ b/internal/cloudapi/v2/depsolve.go @@ -4,9 +4,13 @@ package v2 import ( "context" + "crypto/rand" "fmt" + "math" + "math/big" "time" + "github.com/osbuild/blueprint/pkg/blueprint" "github.com/osbuild/images/pkg/distrofactory" "github.com/osbuild/images/pkg/reporegistry" "github.com/osbuild/images/pkg/rpmmd" @@ -56,14 +60,44 @@ func (request *DepsolveRequest) Depsolve(df *distrofactory.Factory, rr *reporegi } } - // Send the depsolve request to the worker packageSet := make(map[string][]rpmmd.PackageSet, 1) - packageSet["depsolve"] = []rpmmd.PackageSet{ - { - Include: bp.GetPackages(), - EnabledModules: bp.GetEnabledModules(), - Repositories: repos, - }, + + // If there is an optional image_type we use manifest to setup the package/repo list + if request.ImageType != nil { + imageType, err := distroArch.GetImageType(imageTypeFromApiImageType(*request.ImageType, distroArch)) + if err != nil { + return nil, HTTPError(ErrorUnsupportedImageType) + } + + // use the same seed for all images so we get the same IDs + bigSeed, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64)) + if err != nil { + return nil, HTTPError(ErrorFailedToGenerateManifestSeed) + } + manifestSeed := bigSeed.Int64() + + ir := imageRequest{ + imageType: imageType, + repositories: repos, + manifestSeed: manifestSeed, + } + + ibp := blueprint.Convert(bp) + manifestSource, _, err := ir.imageType.Manifest(&ibp, ir.imageOptions, ir.repositories, &ir.manifestSeed) + if err != nil { + return nil, HTTPErrorWithInternal(ErrorFailedToDepsolve, err) + } + + packageSet = manifestSource.GetPackageSetChains() + } else { + // Send the depsolve request to the worker + packageSet["os"] = []rpmmd.PackageSet{ + { + Include: bp.GetPackages(), + EnabledModules: bp.GetEnabledModules(), + Repositories: repos, + }, + } } depsolveJobID, err := workers.EnqueueDepsolve(&worker.DepsolveJob{ @@ -109,5 +143,5 @@ func (request *DepsolveRequest) Depsolve(df *distrofactory.Factory, rr *reporegi } } - return result.PackageSpecs["depsolve"], nil + return result.PackageSpecs["os"], nil } diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index 16d2e8acb..fa8f2b24f 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -92,17 +92,6 @@ func mockDepsolve(t *testing.T, workerServer *worker.Server, wg *sync.WaitGroup, } dJR := &worker.DepsolveJobResult{ PackageSpecs: map[string][]rpmmd.PackageSpec{ - // Used when depsolving a list of packages outside the manifest - // Including it in other responses currently doesn't cause a problem - "depsolve": { - { - Name: "dep-package1", - Version: "1.33", - Release: "2.fc30", - Arch: "x86_64", - Checksum: "sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893", - }, - }, // Used when depsolving a manifest "build": { { @@ -113,6 +102,9 @@ func mockDepsolve(t *testing.T, workerServer *worker.Server, wg *sync.WaitGroup, "os": { { Name: "pkg1", + Version: "1.33", + Release: "2.fc30", + Arch: "x86_64", Checksum: "sha256:e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe", }, }, @@ -1694,7 +1686,7 @@ func TestDepsolveBlueprint(t *testing.T) { "distro": "%[1]s", "enabled_modules": [{ "name": "deps", "stream": "1" }], "packages": [ - { "name": "dep-package", "version": "*" } + { "name": "pkg1", "version": "*" } ]}, "distribution": "%[1]s", "architecture": "%[2]s" @@ -1703,17 +1695,80 @@ func TestDepsolveBlueprint(t *testing.T) { `{ "packages": [ { - "name": "dep-package1", - "type": "rpm", + "name": "pkg1", + "type": "rpm", "version": "1.33", "release": "2.fc30", "arch": "x86_64", - "checksum": "sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893" + "checksum": "sha256:e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe" } ] }`) } +func TestDepsolveImageType(t *testing.T) { + srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false) + defer cancel() + + test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", + "/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(` + { + "blueprint": { + "name": "deptest1", + "version": "0.0.1", + "distro": "%[1]s", + "enabled_modules": [{ "name": "deps", "stream": "1" }], + "packages": [ + { "name": "pkg1", "version": "*" } + ]}, + "distribution": "%[1]s", + "architecture": "%[2]s", + "image_type": "%[3]s" + }`, test_distro.TestDistro1Name, test_distro.TestArch3Name, test_distro.TestImageTypeImageInstaller), + http.StatusOK, + `{ + "packages": [ + { + "name": "pkg1", + "type": "rpm", + "version": "1.33", + "release": "2.fc30", + "arch": "x86_64", + "checksum": "sha256:e50ddb78a37f5851d1a5c37a4c77d59123153c156e628e064b9daa378f45a2fe" + } + ] + }`) +} + +func TestDepsolveImageTypeError(t *testing.T) { + srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false) + defer cancel() + + test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", + "/api/image-builder-composer/v2/depsolve/blueprint", fmt.Sprintf(` + { + "blueprint": { + "name": "deptest1", + "version": "0.0.1", + "distro": "%[1]s", + "enabled_modules": [{ "name": "deps", "stream": "1" }], + "packages": [ + { "name": "pkg1", "version": "*" } + ]}, + "distribution": "%[1]s", + "architecture": "%[2]s", + "image_type": "bad-image-type" + }`, test_distro.TestDistro1Name, test_distro.TestArchName), + http.StatusBadRequest, ` + { + "href": "/api/image-builder-composer/v2/errors/30", + "id": "30", + "kind": "Error", + "code": "IMAGE-BUILDER-COMPOSER-30", + "reason":"Request could not be validated" + }`, "operation_id", "details") +} + func TestDepsolveDistroErrors(t *testing.T) { srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false) defer cancel()