Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cloudapi depsolve (COMPOSER-2131) #4372

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/osbuild-composer/composer.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ func (c *Composer) InitAPI(cert, key string, enableTLS bool, enableMTLS bool, en
TenantProviderFields: c.config.Koji.JWTTenantProviderFields,
}

c.api = cloudapi.NewServer(c.workers, c.distros, c.repos, config)
c.api = cloudapi.NewServer(c.workers, c.distros, c.repos, c.solver, config)

if !enableTLS {
c.apiListener = l
Expand Down
5 changes: 3 additions & 2 deletions internal/cloudapi/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"net/http"

"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/dnfjson"
"github.com/osbuild/images/pkg/reporegistry"

v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
Expand All @@ -14,9 +15,9 @@ type Server struct {
v2 *v2.Server
}

func NewServer(workers *worker.Server, distros *distrofactory.Factory, repos *reporegistry.RepoRegistry, config v2.ServerConfig) *Server {
func NewServer(workers *worker.Server, distros *distrofactory.Factory, repos *reporegistry.RepoRegistry, solver *dnfjson.BaseSolver, config v2.ServerConfig) *Server {
server := &Server{
v2: v2.NewServer(workers, distros, repos, config),
v2: v2.NewServer(workers, distros, repos, solver, config),
}
return server
}
Expand Down
28 changes: 20 additions & 8 deletions internal/cloudapi/v2/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ func (bcpm BlueprintCustomizationsPartitioningMode) String() string {
}
}

// GetCustomizationsFromBlueprintRequest populates a blueprint customization struct
// with the data from the blueprint section of a ComposeRequest, which is similar but
// GetCustomizationsFromBlueprint populates a blueprint customization struct
// with the data from request Blueprint, which is similar but
// slightly different from the Cloudapi's Customizations section
// This starts with a new empty blueprint.Customization object
// If there are no customizations, it returns nil
func (request *ComposeRequest) GetCustomizationsFromBlueprintRequest() (*blueprint.Customizations, error) {
if request.Blueprint.Customizations == nil {
func (rbp *Blueprint) GetCustomizationsFromBlueprintRequest() (*blueprint.Customizations, error) {
if rbp.Customizations == nil {
return nil, nil
}

c := &blueprint.Customizations{}
rbpc := request.Blueprint.Customizations
rbpc := rbp.Customizations

if rbpc.Hostname != nil {
c.Hostname = rbpc.Hostname
Expand Down Expand Up @@ -499,8 +499,12 @@ func (request *ComposeRequest) GetBlueprintFromCompose() (blueprint.Blueprint, e
return bp, err
}

return ConvertRequestBP(*request.Blueprint)
}

// ConvertRequestBP takes a request Blueprint and returns a composer blueprint.Blueprint
func ConvertRequestBP(rbp Blueprint) (blueprint.Blueprint, error) {
var bp blueprint.Blueprint
rbp := request.Blueprint

// Copy all the parts from the OpenAPI Blueprint into a blueprint.Blueprint
// NOTE: Openapi fields may be nil, test for that first.
Expand All @@ -514,6 +518,9 @@ func (request *ComposeRequest) GetBlueprintFromCompose() (blueprint.Blueprint, e
if rbp.Distro != nil {
bp.Distro = *rbp.Distro
}
if rbp.Architecture != nil {
bp.Arch = *rbp.Architecture
}

if rbp.Packages != nil {
for _, pkg := range *rbp.Packages {
Expand Down Expand Up @@ -553,7 +560,7 @@ func (request *ComposeRequest) GetBlueprintFromCompose() (blueprint.Blueprint, e
}
}

customizations, err := request.GetCustomizationsFromBlueprintRequest()
customizations, err := rbp.GetCustomizationsFromBlueprintRequest()
if err != nil {
return bp, err
}
Expand Down Expand Up @@ -1144,7 +1151,12 @@ func (request *ComposeRequest) GetImageRequests(distroFactory *distrofactory.Fac
}
var irs []imageRequest
for _, ir := range *request.ImageRequests {
arch, err := distribution.GetArch(ir.Architecture)
reqArch := ir.Architecture
// If there is an architecture in the blueprint it overrides the request's arch
if len(bp.Arch) > 0 {
reqArch = bp.Arch
}
arch, err := distribution.GetArch(reqArch)
if err != nil {
return nil, HTTPError(ErrorUnsupportedArchitecture)
}
Expand Down
27 changes: 27 additions & 0 deletions internal/cloudapi/v2/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,33 @@ func TestGetImageRequests_BlueprintDistro(t *testing.T) {
assert.Equal(t, got[0].blueprint.Distro, "fedora-39")
}

// TestGetImageRequests_BlueprintArch test to make sure blueprint architecture overrides
// the request arch
func TestGetImageRequests_BlueprintArch(t *testing.T) {
uo := UploadOptions(struct{}{})
request := &ComposeRequest{
Distribution: "fedora-40",
ImageRequest: &ImageRequest{
Architecture: "x86_64",
ImageType: ImageTypesAws,
UploadOptions: &uo,
Repositories: []Repository{},
},
Blueprint: &Blueprint{
Name: "arch-test",
Architecture: common.ToPtr("aarch64"),
},
}
// NOTE: current directory is the location of this file, back up so it can use ./repositories/
rr, err := reporegistry.New([]string{"../../../"})
require.NoError(t, err)
got, err := request.GetImageRequests(distrofactory.NewDefault(), rr)
assert.NoError(t, err)
require.Len(t, got, 1)
require.Greater(t, len(got[0].repositories), 0)
assert.Equal(t, got[0].blueprint.Arch, "aarch64")
}

func TestOpenSCAPTailoringOptions(t *testing.T) {
cr := ComposeRequest{
Customizations: &Customizations{
Expand Down
82 changes: 82 additions & 0 deletions internal/cloudapi/v2/depsolve.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package v2

import (
"log"

"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distrofactory"
"github.com/osbuild/images/pkg/dnfjson"
"github.com/osbuild/images/pkg/reporegistry"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/sbom"
)

func (request *DepsolveRequest) DepsolveBlueprint(df *distrofactory.Factory, rr *reporegistry.RepoRegistry, solver *dnfjson.BaseSolver) ([]rpmmd.PackageSpec, error) {
bp, err := ConvertRequestBP(request.Blueprint)
if err != nil {
return nil, err
}

// Distro name, in order of priority
// bp.Distro
// host distro
var originalDistroName string
if len(bp.Distro) > 0 {
originalDistroName = bp.Distro
} else {
originalDistroName, err = distro.GetHostDistroName()
if err != nil {
return nil, HTTPErrorWithInternal(ErrorUnsupportedDistribution, err)
}
}

distribution := df.GetDistro(originalDistroName)
if distribution == nil {
return nil, HTTPError(ErrorUnsupportedDistribution)
}

var originalArchName string
if len(bp.Arch) > 0 {
originalArchName = bp.Arch
} else {
originalArchName = arch.Current().String()
}
distroArch, err := distribution.GetArch(originalArchName)
if err != nil {
return nil, HTTPErrorWithInternal(ErrorUnsupportedArchitecture, err)
}

// Get the repositories to use for depsolving
// Either the list passed in with the request, or the defaults for the distro+arch
var repos []rpmmd.RepoConfig
if request.Repositories != nil {
repos, err = convertRepos(*request.Repositories, []Repository{}, []string{})
if err != nil {
// Error comes from genRepoConfig and is already an HTTPError
return nil, err
}
} else {
repos, err = rr.ReposByArchName(originalDistroName, distroArch.Name(), false)
if err != nil {
return nil, HTTPErrorWithInternal(ErrorInvalidRepository, err)
}
}

s := solver.NewWithConfig(
distribution.ModulePlatformID(),
distribution.Releasever(),
distroArch.Name(),
distribution.Name())
solved, err := s.Depsolve([]rpmmd.PackageSet{{Include: bp.GetPackages(), Repositories: repos}}, sbom.StandardTypeNone)
if err != nil {
return nil, HTTPErrorWithInternal(ErrorFailedToDepsolve, err)
}

if err := solver.CleanCache(); err != nil {
// log and ignore
log.Printf("Error during rpm repo cache cleanup: %s", err.Error())
}

return solved.Packages, nil
}
45 changes: 44 additions & 1 deletion internal/cloudapi/v2/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ func stagesToPackageMetadata(stages []osbuild.RPMStageMetadata) []PackageMetadat
Release: rpm.Release,
Epoch: rpm.Epoch,
Arch: rpm.Arch,
Sigmd5: rpm.SigMD5,
Sigmd5: common.ToPtr(rpm.SigMD5),
Signature: osbuild.RPMPackageMetadataToSignature(rpm),
},
)
Expand Down Expand Up @@ -1354,3 +1354,46 @@ func uploadStatusFromJobStatus(js *worker.JobStatus, je *clienterrors.Error) Upl
}
return UploadStatusValueSuccess
}

// PostDepsolveBlueprint depsolves the packages in a blueprint and returns
// the results as a list of rpmmd.PackageSpecs
func (h *apiHandlers) PostDepsolveBlueprint(ctx echo.Context) error {
var request DepsolveRequest
err := ctx.Bind(&request)
if err != nil {
return err
}

deps, err := request.DepsolveBlueprint(h.server.distros, h.server.repos, h.server.solver)
if err != nil {
return err
}

return ctx.JSON(http.StatusOK,
DepsolveResponse{
Packages: packageSpecToPackageMetadata(deps),
})
}

func packageSpecToPackageMetadata(pkgspecs []rpmmd.PackageSpec) []PackageMetadata {
packages := make([]PackageMetadata, 0)
for _, rpm := range pkgspecs {
// Set epoch if it is not 0
var epoch *string
if rpm.Epoch > 0 {
epoch = common.ToPtr(strconv.FormatUint(uint64(rpm.Epoch), 10))
}
packages = append(packages,
PackageMetadata{
Type: "rpm",
Name: rpm.Name,
Version: rpm.Version,
Release: rpm.Release,
Epoch: epoch,
Arch: rpm.Arch,
Checksum: common.ToPtr(rpm.Checksum),
},
)
}
return packages
}
Loading
Loading