From 4720e57e50d078295dab762cb4852511ebf316bf Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 17 Sep 2024 13:55:28 -0700 Subject: [PATCH 1/7] cloudapi: Add architecture support to the optional Blueprint If included it overrides the architecture in the compose image request. Related: RHEL-60125 --- internal/cloudapi/v2/compose.go | 10 +- internal/cloudapi/v2/compose_test.go | 27 ++ internal/cloudapi/v2/openapi.v2.gen.go | 371 +++++++++++++------------ internal/cloudapi/v2/openapi.v2.yml | 6 + 4 files changed, 230 insertions(+), 184 deletions(-) diff --git a/internal/cloudapi/v2/compose.go b/internal/cloudapi/v2/compose.go index 5d270f6af1..2bb761de4e 100644 --- a/internal/cloudapi/v2/compose.go +++ b/internal/cloudapi/v2/compose.go @@ -514,6 +514,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 { @@ -1144,7 +1147,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) } diff --git a/internal/cloudapi/v2/compose_test.go b/internal/cloudapi/v2/compose_test.go index f7f8dc7b78..8051f26342 100644 --- a/internal/cloudapi/v2/compose_test.go +++ b/internal/cloudapi/v2/compose_test.go @@ -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{ diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index c76902471c..2471b13960 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -231,6 +231,10 @@ type AzureUploadStatus struct { // Blueprint defines model for Blueprint. type Blueprint struct { + // Architecture to use for the compose. If left empty the host arch + // will be used. + Architecture *string `json:"architecture,omitempty"` + // Container images to embed into the final artfact Containers *[]Container `json:"containers,omitempty"` Customizations *BlueprintCustomizations `json:"customizations,omitempty"` @@ -1494,189 +1498,190 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "qo1G0xneTHt/vnxsFHb2H33HnbQbT48ng5fJ2aBLCp/72+/X/ftR76RcPHytDFs7V0f7l0Vy/uXz/l3J", - "C0btz6+doF15OGf7Fa9yHLjCP7s9PD07F17t8KBLSuz4/UuDdkoTf/exVT9vHNgXzebV5KXxwunDXX3n", - "8S5ofi70yAvroNvy+e1Vsz+5bu5sP+zWa/jqvku8Wvtzj98cjHea5XPm2o2L6sVBQCdPpTYWx/CpenZz", - "fi8+dw5hqYr5Y/u4+fJOd64f6/eV06thrdglzuuDUy9fFnpe+fC9vdOpVx4OD3old/RSbbmjN6f1eoac", - "Uun9y+Obxx7bT6enzf7ovf/ZvWxvB2/OSZe8vBVOixP3qXyOe8ds+7jRmFzt3j2wxlN73L4oHlovnfr4", - "sEnehu2DYPLqPYzvR5f7X4LD1n39ClUeu+QC35X6p5d1bu8c+PzorXbx+YtNLshN+/MJe+lcnx1UvAfm", - "Nmxy2BnYj/f1l6eh/zA4mPBKYXcXXXXJYFhk52RSfLkcD2HQL+C7+pW1/WV0MXw5v704dWp3u/dnk9Pg", - "4UG8j7+Ql4vL2sPt0f7rWZU/Ue/iokv6otc5KX2uTXq3D4VGZbTfg2+3D2Wxc/d++WK9o2H76RDD88vd", - "88KJddps3ZZujurb9fKB3XAPj3btLhmWnRv82L5pQHhaPD1tvJ+Mboe3p+fnzln58eYRn1zeT8qicjo5", - "6nMGvdq43Xy46g+uUWtyvt95Ou2SEfMv3ese6vPObm2n0y/vX7YC5/2JNWv3bwfts+GTczso3R+P2q0b", - "0py8D28m24d35ddrHz/UdqWMGly3vjyxM2qdVc7O27sF/H5607l1xctF47cu+e2639npEnW6HF4eLDt6", - "FpS7ogw9c+6mH9I/qx6ufvRnqTv4ox4Bilf/SfW6yPFCx4YuEaSc6jGtCHKp0HCgTK5YRomqPNQlv4aR", - "TJ9SqxDN5RSEZWbphpW2PtaPnnSVgwWe8jVLE5hHQzezq1NVyYZtRxd1oc/VPO8DAzGgDL8jW9kz8/nt", - "a73W02g/YDG8Oqne1XeqhzbfvyMT0av0xqNbxzlxb9ze4xd3h5SKo90FdW5T0+Tv9BtHkfmj86TM46uS", - "pJKOIdvDZHXEN1dBBRJPadbx2pnLH5CBDHqT2Fs6KdVxw2KIdrokIi3dpfQhqckroSF9FbTFNwbGg3y4", - "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", + "/zLRcrtUygAA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index 95c61bfc83..3fa0bc3d12 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -1400,6 +1400,12 @@ components: description: | The distribution to use for the compose. If left empty the host distro will be used. + architecture: + type: string + example: 'x86_64' + description: | + Architecture to use for the compose. If left empty the host arch + will be used. packages: type: array description: Packages to be installed From 66afb709feb0239790cffb0f85f973479ddd74b3 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 13 Sep 2024 14:48:13 -0700 Subject: [PATCH 2/7] cloudapi: Add /depsolve/blueprint route This will allow depsolving blueprints and returning package metadata for the dependencies. Related: RHEL-60125 --- internal/cloudapi/v2/openapi.v2.gen.go | 409 ++++++++++++++----------- internal/cloudapi/v2/openapi.v2.yml | 71 +++++ 2 files changed, 295 insertions(+), 185 deletions(-) diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 2471b13960..926827849d 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -637,6 +637,18 @@ type DNFPluginConfig struct { Enabled *bool `json:"enabled,omitempty"` } +// DepsolveRequest defines model for DepsolveRequest. +type DepsolveRequest struct { + Blueprint Blueprint `json:"blueprint"` + Repositories *[]Repository `json:"repositories,omitempty"` +} + +// DepsolveResponse defines model for DepsolveResponse. +type DepsolveResponse struct { + // Package list including NEVRA + Packages []PackageMetadata `json:"packages"` +} + // A custom directory to create in the final artifact. type Directory struct { // Ensure that the parent directories exist @@ -1020,7 +1032,10 @@ type PackageGroup struct { // PackageMetadata defines model for PackageMetadata. type PackageMetadata struct { - Arch string `json:"arch"` + Arch string `json:"arch"` + + // Optional package checksum using ALGO:HASH form + Checksum *string `json:"checksum,omitempty"` Epoch *string `json:"epoch,omitempty"` Name string `json:"name"` Release string `json:"release"` @@ -1211,6 +1226,9 @@ type PostComposeJSONBody ComposeRequest // PostCloneComposeJSONBody defines parameters for PostCloneCompose. type PostCloneComposeJSONBody CloneComposeBody +// PostDepsolveBlueprintJSONBody defines parameters for PostDepsolveBlueprint. +type PostDepsolveBlueprintJSONBody DepsolveRequest + // GetErrorListParams defines parameters for GetErrorList. type GetErrorListParams struct { // Page index @@ -1226,6 +1244,9 @@ type PostComposeJSONRequestBody PostComposeJSONBody // PostCloneComposeJSONRequestBody defines body for PostCloneCompose for application/json ContentType. type PostCloneComposeJSONRequestBody PostCloneComposeJSONBody +// PostDepsolveBlueprintJSONRequestBody defines body for PostDepsolveBlueprint for application/json ContentType. +type PostDepsolveBlueprintJSONRequestBody PostDepsolveBlueprintJSONBody + // ServerInterface represents all server handlers. type ServerInterface interface { // The status of a cloned compose @@ -1252,6 +1273,9 @@ type ServerInterface interface { // Get the SBOMs for a compose. // (GET /composes/{id}/sboms) GetComposeSBOMs(ctx echo.Context, id string) error + // Depsolve one or more blueprints + // (POST /depsolve/blueprint) + PostDepsolveBlueprint(ctx echo.Context) error // Get a list of all possible errors // (GET /errors) GetErrorList(ctx echo.Context, params GetErrorListParams) error @@ -1397,6 +1421,17 @@ func (w *ServerInterfaceWrapper) GetComposeSBOMs(ctx echo.Context) error { return err } +// PostDepsolveBlueprint converts echo context to params. +func (w *ServerInterfaceWrapper) PostDepsolveBlueprint(ctx echo.Context) error { + var err error + + ctx.Set(BearerScopes, []string{""}) + + // Invoke the callback with all the unmarshalled arguments + err = w.Handler.PostDepsolveBlueprint(ctx) + return err +} + // GetErrorList converts echo context to params. func (w *ServerInterfaceWrapper) GetErrorList(ctx echo.Context) error { var err error @@ -1489,6 +1524,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/composes/:id/manifests", wrapper.GetComposeManifests) router.GET(baseURL+"/composes/:id/metadata", wrapper.GetComposeMetadata) router.GET(baseURL+"/composes/:id/sboms", wrapper.GetComposeSBOMs) + router.POST(baseURL+"/depsolve/blueprint", wrapper.PostDepsolveBlueprint) router.GET(baseURL+"/errors", wrapper.GetErrorList) router.GET(baseURL+"/errors/:id", wrapper.GetError) router.GET(baseURL+"/openapi", wrapper.GetOpenapi) @@ -1498,190 +1534,193 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "/zLRcrtUygAA", + "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", + "8otzWjsZN6+bV83dLhF7B4eP99cjy9sfnOydlU46zcajdzfsVY6d05uz0umX3Qm8LzkWcZvh79bR8Qh6", + "d892qzbqEsuzPuOr44vd3bPdVrNZPcD7++hoy2POwVE9uONXp2dn5eJDzXp0yOtD46DpqTPUOhw3Dlrj", + "YbtLdsftw4Mretxq8tbu7kOrOd5vHQ32WwfVZrM1GF5Ne38+f2gW6rsP/sCddJqPD0fO8+TE6ZLC5/7W", + "22X/btQ7Khf3XyrDdv3iYPe8SE6/fN69LXnBqPP55SboVO5P2W7FqxwGrvBPrvePT06FV9vf65ISO3z7", + "0qQ3pYm//dBunDb37LNW62Ly3Hzm9P62UX+4DVqfCz3yzG7Qdfn0+qLVn1y26lv3240avrjrEq/W+dzj", + "V3vjeqt8yly7eVY92wvo5LHUweIQPlZPrk7vxOebfViqYv7QOWw9v9H65UPjrnJ8MawVu2Twcj9olM8L", + "Pa+8/9ap3zQq9/t7vZI7eq623dHroP1yggal0tuXh1ePPXQej49b/dFb/7N73tkKXgdHXfL8WjguTtzH", + "8inuHbKtw2ZzcrF9e8+aj51x56y4bz3fNMb7LfI67OwFkxfvfnw3Ot/9Euy37xoXqPLQJWf4ttQ/Pm9w", + "u77n84PX2tnnLzY5I1edz0fs+ebyZK/i3TO3aZP9G8d+uGs8Pw79e2dvwiuF7W100SXOsMhOyaT4fD4e", + "wqBfwLeNC2vry+hs+Hx6fXY8qN1u351MjoP7e/E2/kKez85r99cHuy8nVf5IvbOzLumL3s1R6XNt0ru+", + "LzQro90efL2+L4v67dv5s/WGhp3HfQxPz7dPC0fWcat9Xbo6aGw1ynt2090/2La7ZFgeXOGHzlUTwuPi", + "8XHz7Wh0Pbw+Pj0dnJQfrh7w0fndpCwqx5ODPmfQq407rfuLvnOJ2pPT3ZvH4y4ZMf/cveyhPr/ZrtVv", + "+uXd83YweHtkrdrd617nZPg4uHZKd4ejTvuKtCZvw6vJ1v5t+eXSx/e1bcmjnMv2l0d2Qq2TyslpZ7uA", + "346vbq5d8XzW/L1Lfr/s39S7RN0u++d7y66eBdXUKENPnLvpl/TPopqr35Raar7+qDem4sWlUq1EcrxQ", + "Z9cVqJQTICYVQS4FGg6UyhXLgFGFrbrk1zDy6rfUIldzORBhFWO6YSG3j7X7J037YIFlf81SCuZN2s30", + "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index 3fa0bc3d12..e6c5a46f61 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -397,6 +397,50 @@ paths: schema: $ref: '#/components/schemas/Error' + /depsolve/blueprint: + post: + operationId: postDepsolveBlueprint + summary: Depsolve one or more blueprints + security: + - Bearer: [] + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/DepsolveRequest' + responses: + '200': + description: Depsolved package list + content: + application/json: + schema: + $ref: '#/components/schemas/DepsolveResponse' + '400': + description: Invalid depsolve request + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '401': + description: Auth token is invalid + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '403': + description: Unauthorized to perform operation + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + '500': + description: Unexpected error occurred + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + /errors/{id}: get: operationId: getError @@ -830,6 +874,10 @@ components: type: string signature: type: string + checksum: + type: string + description: 'Optional package checksum using ALGO:HASH form' + example: 'sha256:525788de3dd44497c27d4172568366b20380a6b6707f0a1970473e4d97046a4f' ComposeRequest: additionalProperties: false @@ -2192,6 +2240,29 @@ components: - $ref: '#/components/schemas/ObjectReference' - $ref: '#/components/schemas/UploadStatus' + DepsolveRequest: + additionalProperties: false + required: + - blueprint + properties: + repositories: + type: array + items: + $ref: '#/components/schemas/Repository' + blueprint: + $ref: '#/components/schemas/Blueprint' + + DepsolveResponse: + type: object + required: + - packages + properties: + packages: + type: array + items: + $ref: '#/components/schemas/PackageMetadata' + description: 'Package list including NEVRA' + parameters: page: name: page From 134e434cbf10ecdfbee6e275e2f11a2b6ed4f1bb Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 13 Sep 2024 16:25:14 -0700 Subject: [PATCH 3/7] cloudapi: Move GetCustomizationsFromBlueprintRequest This function only depends on the Blueprint (cloudapi request type, not the internal/blueprint) so move it to a function on that so that it can be reused by other users of the cloudapi Blueprint. Related: RHEL-60125 --- internal/cloudapi/v2/compose.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/internal/cloudapi/v2/compose.go b/internal/cloudapi/v2/compose.go index 2bb761de4e..80e906d3b9 100644 --- a/internal/cloudapi/v2/compose.go +++ b/internal/cloudapi/v2/compose.go @@ -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 @@ -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. @@ -556,7 +560,7 @@ func (request *ComposeRequest) GetBlueprintFromCompose() (blueprint.Blueprint, e } } - customizations, err := request.GetCustomizationsFromBlueprintRequest() + customizations, err := rbp.GetCustomizationsFromBlueprintRequest() if err != nil { return bp, err } From 2d36346c9c40ce95ea5f58ce166222885f439ed0 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Mon, 16 Sep 2024 15:31:37 -0700 Subject: [PATCH 4/7] cloudapi: Add solver to Server Related: RHEL-60125 --- cmd/osbuild-composer/composer.go | 2 +- internal/cloudapi/server.go | 5 +++-- internal/cloudapi/v2/server.go | 5 ++++- internal/cloudapi/v2/v2_test.go | 6 +++++- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/cmd/osbuild-composer/composer.go b/cmd/osbuild-composer/composer.go index 485248d6fe..a72eb738ea 100644 --- a/cmd/osbuild-composer/composer.go +++ b/cmd/osbuild-composer/composer.go @@ -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 diff --git a/internal/cloudapi/server.go b/internal/cloudapi/server.go index 43e11c847a..c8be749475 100644 --- a/internal/cloudapi/server.go +++ b/internal/cloudapi/server.go @@ -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" @@ -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 } diff --git a/internal/cloudapi/v2/server.go b/internal/cloudapi/v2/server.go index 741bea64f7..457c27e166 100644 --- a/internal/cloudapi/v2/server.go +++ b/internal/cloudapi/v2/server.go @@ -23,6 +23,7 @@ import ( "github.com/osbuild/images/pkg/container" "github.com/osbuild/images/pkg/distrofactory" + "github.com/osbuild/images/pkg/dnfjson" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/ostree" "github.com/osbuild/images/pkg/reporegistry" @@ -43,6 +44,7 @@ type Server struct { repos *reporegistry.RepoRegistry config ServerConfig router routers.Router + solver *dnfjson.BaseSolver goroutinesCtx context.Context goroutinesCtxCancel context.CancelFunc @@ -54,7 +56,7 @@ type ServerConfig struct { JWTEnabled bool } -func NewServer(workers *worker.Server, distros *distrofactory.Factory, repos *reporegistry.RepoRegistry, config ServerConfig) *Server { +func NewServer(workers *worker.Server, distros *distrofactory.Factory, repos *reporegistry.RepoRegistry, solver *dnfjson.BaseSolver, config ServerConfig) *Server { ctx, cancel := context.WithCancel(context.Background()) spec, err := GetSwagger() if err != nil { @@ -77,6 +79,7 @@ func NewServer(workers *worker.Server, distros *distrofactory.Factory, repos *re repos: repos, config: config, router: router, + solver: solver, goroutinesCtx: ctx, goroutinesCtxCancel: cancel, diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index 8df071bfe7..8615b6d281 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -15,6 +15,7 @@ import ( "github.com/osbuild/images/pkg/distro/test_distro" "github.com/osbuild/images/pkg/distrofactory" + "github.com/osbuild/images/pkg/dnfjson" "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/ostree/mock_ostree_repo" "github.com/osbuild/images/pkg/reporegistry" @@ -81,11 +82,14 @@ func newV2Server(t *testing.T, dir string, depsolveChannels []string, enableJWT require.Nil(t, err) require.NotNil(t, repos) + solver := dnfjson.NewBaseSolver("") // test solver doesn't need a cache dir + require.NotNil(t, solver) + config := v2.ServerConfig{ JWTEnabled: enableJWT, TenantProviderFields: []string{"rh-org-id", "account_id"}, } - v2Server := v2.NewServer(workerServer, distros, repos, config) + v2Server := v2.NewServer(workerServer, distros, repos, solver, config) require.NotNil(t, v2Server) t.Cleanup(v2Server.Shutdown) From 0e90aa6f5742d3c1878be70ab8cdcd02bd544656 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Wed, 18 Sep 2024 11:23:02 -0700 Subject: [PATCH 5/7] cloudapi: Make sigmd5 in PackageMetadata optional In order to reuse PackageMetadata with DepsolveResponse and not include unused fields this changes the sigmd5 entry to an optional field. This doesn't effect the use of PackageMetadata in the Compose response since it is always set, and it allows it to be omitted in the response for depsolving. Also adds a basic test for stagesToPackageMetadata Related: RHEL-60125 --- internal/cloudapi/v2/handler.go | 2 +- internal/cloudapi/v2/openapi.v2.gen.go | 138 +++++++++++------------ internal/cloudapi/v2/openapi.v2.yml | 1 - internal/cloudapi/v2/v2_internal_test.go | 119 +++++++++++++++++++ 4 files changed, 189 insertions(+), 71 deletions(-) diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index 34ecd88212..378bf55b0c 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -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), }, ) diff --git a/internal/cloudapi/v2/openapi.v2.gen.go b/internal/cloudapi/v2/openapi.v2.gen.go index 926827849d..0ffb2147c1 100644 --- a/internal/cloudapi/v2/openapi.v2.gen.go +++ b/internal/cloudapi/v2/openapi.v2.gen.go @@ -1039,7 +1039,7 @@ type PackageMetadata struct { Epoch *string `json:"epoch,omitempty"` Name string `json:"name"` Release string `json:"release"` - Sigmd5 string `json:"sigmd5"` + Sigmd5 *string `json:"sigmd5,omitempty"` Signature *string `json:"signature,omitempty"` Type string `json:"type"` Version string `json:"version"` @@ -1653,74 +1653,74 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/internal/cloudapi/v2/openapi.v2.yml b/internal/cloudapi/v2/openapi.v2.yml index e6c5a46f61..ce5550bbdc 100644 --- a/internal/cloudapi/v2/openapi.v2.yml +++ b/internal/cloudapi/v2/openapi.v2.yml @@ -856,7 +856,6 @@ components: - version - release - arch - - sigmd5 properties: type: type: string diff --git a/internal/cloudapi/v2/v2_internal_test.go b/internal/cloudapi/v2/v2_internal_test.go index be52bab608..ed0949e6c6 100644 --- a/internal/cloudapi/v2/v2_internal_test.go +++ b/internal/cloudapi/v2/v2_internal_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/osbuild/images/pkg/osbuild" "github.com/osbuild/images/pkg/rpmmd" "github.com/osbuild/osbuild-composer/internal/common" ) @@ -232,3 +233,121 @@ func TestRepoConfigConversion(t *testing.T) { assert.EqualError(err, tc.err) } } + +func TestStagesToPackageMetadata(t *testing.T) { + assert := assert.New(t) + type testCase struct { + stages []osbuild.RPMStageMetadata + pkgs []PackageMetadata + } + testCases := []testCase{ + { + stages: []osbuild.RPMStageMetadata{ + { + Packages: []osbuild.RPMPackageMetadata{ + { + Name: "vim-minimal", + Version: "8.0.1763", + Release: "15.el8", + Epoch: common.ToPtr("2"), + Arch: "x86_64", + SigMD5: "v", + SigPGP: "v", + SigGPG: "v", + }, + { + Name: "unique", + Version: "1.90", + Release: "10", + Epoch: nil, + Arch: "aarch64", + SigMD5: "v", + SigPGP: "v", + SigGPG: "v", + }, + }, + }, + }, + pkgs: []PackageMetadata{ + { + Type: "rpm", + Name: "vim-minimal", + Version: "8.0.1763", + Release: "15.el8", + Epoch: common.ToPtr("2"), + Arch: "x86_64", + Sigmd5: common.ToPtr("v"), + Signature: common.ToPtr("v"), + }, + { + Type: "rpm", + Name: "unique", + Version: "1.90", + Release: "10", + Epoch: nil, + Arch: "aarch64", + Sigmd5: common.ToPtr("v"), + Signature: common.ToPtr("v"), + }, + }, + }, + { + stages: []osbuild.RPMStageMetadata{ + { + Packages: []osbuild.RPMPackageMetadata{ + { + Name: "vim-minimal", + Version: "8.0.1763", + Release: "15.el8", + Epoch: common.ToPtr("2"), + Arch: "x86_64", + SigMD5: "v", + SigPGP: "v", + SigGPG: "v", + }, + }, + }, + { + Packages: []osbuild.RPMPackageMetadata{ + { + Name: "unique", + Version: "1.90", + Release: "10", + Epoch: nil, + Arch: "aarch64", + SigMD5: "v", + SigPGP: "v", + SigGPG: "v", + }, + }, + }, + }, + pkgs: []PackageMetadata{ + { + Type: "rpm", + Name: "vim-minimal", + Version: "8.0.1763", + Release: "15.el8", + Epoch: common.ToPtr("2"), + Arch: "x86_64", + Sigmd5: common.ToPtr("v"), + Signature: common.ToPtr("v"), + }, + { + Type: "rpm", + Name: "unique", + Version: "1.90", + Release: "10", + Epoch: nil, + Arch: "aarch64", + Sigmd5: common.ToPtr("v"), + Signature: common.ToPtr("v"), + }, + }, + }, + } + + for idx, tc := range testCases { + assert.Equal(tc.pkgs, stagesToPackageMetadata(tc.stages), "mismatch in test case %d", idx) + } +} From 8e34b377c5f271eef4b71504e4c615f11ff49b72 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Tue, 27 Aug 2024 17:01:06 -0700 Subject: [PATCH 6/7] v2_test: Add a test-distro-1 repository This also adds an actual repository json file for the test-disro. Without this the repo.ListDistros() function doesn't return any actual distros. Related: RHEL-60125 --- internal/cloudapi/v2/v2_test.go | 5 ++++- test/data/repositories/test-distro-1.json | 26 +++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 test/data/repositories/test-distro-1.json diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index 8615b6d281..a4d0c73009 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -78,9 +78,12 @@ func newV2Server(t *testing.T, dir string, depsolveChannels []string, enableJWT distros := distrofactory.NewTestDefault() require.NotNil(t, distros) - repos, err := reporegistry.New([]string{"../../../test/data"}) + // The path is relative to the running test + testReposPath := []string{"../../../test/data"} + repos, err := reporegistry.New(testReposPath) require.Nil(t, err) require.NotNil(t, repos) + require.Greater(t, len(repos.ListDistros()), 0) solver := dnfjson.NewBaseSolver("") // test solver doesn't need a cache dir require.NotNil(t, solver) diff --git a/test/data/repositories/test-distro-1.json b/test/data/repositories/test-distro-1.json new file mode 100644 index 0000000000..bdfd3bc15e --- /dev/null +++ b/test/data/repositories/test-distro-1.json @@ -0,0 +1,26 @@ +{ + "test_arch": [ + { + "name": "test-distro", + "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-x86_64-rawhide-20240101", + "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGPQTCwBEADFUL0EQLzwpKHtlPkacVI156F2LnWp6K69g/6yzllidHI3b7EV\nQgQ9/Kdou6wNuOahNKa6WcEi6grEXexD7pAcu4xdRUp79XxQy5pC7Aq2/Dwf0vRL\n2y0kqof+C7iSzhHsfLoaqKKeh2njAo1KLZXYTHAWAMbXEyO/FJevaHLXe2+yYd7j\nluD58gyXgGDXXJ2lymLqs2jobjWdmGPNZGFl36RP3Dnk0FpbdH78kyIIsc2foYuF\n00rnuumwCtK3V58VOZo6IkaYk2irdyeetmJjVHwLHwJB3EaAwGy9Z2oAH3LxxFfk\nrQb0DH0Nzb3fpEziopOOqSi+6guV4RHUKAkCUMu+Mo5XwFVPUAIfNRTVqoIaEasC\nWO26lhkB87wwIvyb/TPGSeh6laHPRf0QOUOLkugdkSHoaJFWoTCcu9Y4aeDpf+ZQ\nfMVmkJNRS1tXONgz+pDk1rro/tNrkusYG18xjvSZTB0P0C4b4+jgK5l7me0NU6G3\nWw/hIng5lxWfXgE9bpxlN834v1xy5Z3v17guJu1ec/jzKzQQ4356wyegXURjYoWe\nawcnK1S+9gxivnkOk1bGLNxrEh5vB6PDcI1VQ1ECH50EHyvE1IXJDaaStdAkacv2\nqHcd15CnlBW1LYFj0CHs/sGu9FD0iSF95OVRX4gjg9Wa4f8KvtEO/f+FeQARAQAB\ntDFGZWRvcmEgKDQwKSA8ZmVkb3JhLTQwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQJOBBMBCAA4FiEEEV35rvhXhT7oRF0KBydwfqFbecwFAmPQTCwCGw8FCwkI\nBwIGFQoJCAsCBBYCAwECHgECF4AACgkQBydwfqFbecxJOw//XaoJG3zN01bVM63H\nnFmMW/EnLzKrZqH8ZNq8CP9ycoc4q8SYcMprHKG9jufzj5/FhtpYecp3kBMpSYHt\nVu46LS9NajJDwdfvUMezVbieNIQ8icTR5s5IUYFlc47eG6PRe3k0n5fOPcIb6q82\nbyrK3dQnanOcVdoGU7QO9LAAHO9hg0zgZa0MxQAlDQov3dZcr7u7qGcQmU5JzcRS\nJgfDxHxDuMjmq6Kd0/UwD00kd2ptZgRls0ntXdm9CZGtQ/Q0baJ3eRzccpd/8bxy\nRWF9MnOdmV6ojcFKYECjEzcuheUlcKQH9rLkeBSfgrIlK3L7LG8bg5ouZLdx17rQ\nXABNQGmJTaGAiEnS/48G3roMS8R7fhUljcKr6t63QQQJ2qWdPvI6EMC2xKZsLHK4\nXiUvrmJpUprvEQSKBUOf/2zuXDBshtAnoKh7h5aG+TvozL4yNG5DKpSH3MRj1E43\nKoMsP/GN/X5h+vJnvhiCWxNMPP81Op0czBAgukBm627FTnsvieJOOrzyxb1s75+W\n56gJombmhzUfzr88AYY9mFy7diTw/oldDZcfwa8rvOAGJVDlyr2hqkLoGl+5jPex\nslt3NF4caE/wP9wPMgFRkmMOr8eiRhjlWLrO6mQdBp7Qsj3kEXioP+CZ1cv/sbaK\n4DM7VidB4PLrMFQMaf0LpjpC2DM=\n=wOl2\n-----END PGP PUBLIC KEY BLOCK-----", + "check_gpg": true + } + ], + "test_arch2": [ + { + "name": "test-distro", + "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-aarch64-rawhide-20240101", + "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGPQTCwBEADFUL0EQLzwpKHtlPkacVI156F2LnWp6K69g/6yzllidHI3b7EV\nQgQ9/Kdou6wNuOahNKa6WcEi6grEXexD7pAcu4xdRUp79XxQy5pC7Aq2/Dwf0vRL\n2y0kqof+C7iSzhHsfLoaqKKeh2njAo1KLZXYTHAWAMbXEyO/FJevaHLXe2+yYd7j\nluD58gyXgGDXXJ2lymLqs2jobjWdmGPNZGFl36RP3Dnk0FpbdH78kyIIsc2foYuF\n00rnuumwCtK3V58VOZo6IkaYk2irdyeetmJjVHwLHwJB3EaAwGy9Z2oAH3LxxFfk\nrQb0DH0Nzb3fpEziopOOqSi+6guV4RHUKAkCUMu+Mo5XwFVPUAIfNRTVqoIaEasC\nWO26lhkB87wwIvyb/TPGSeh6laHPRf0QOUOLkugdkSHoaJFWoTCcu9Y4aeDpf+ZQ\nfMVmkJNRS1tXONgz+pDk1rro/tNrkusYG18xjvSZTB0P0C4b4+jgK5l7me0NU6G3\nWw/hIng5lxWfXgE9bpxlN834v1xy5Z3v17guJu1ec/jzKzQQ4356wyegXURjYoWe\nawcnK1S+9gxivnkOk1bGLNxrEh5vB6PDcI1VQ1ECH50EHyvE1IXJDaaStdAkacv2\nqHcd15CnlBW1LYFj0CHs/sGu9FD0iSF95OVRX4gjg9Wa4f8KvtEO/f+FeQARAQAB\ntDFGZWRvcmEgKDQwKSA8ZmVkb3JhLTQwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQJOBBMBCAA4FiEEEV35rvhXhT7oRF0KBydwfqFbecwFAmPQTCwCGw8FCwkI\nBwIGFQoJCAsCBBYCAwECHgECF4AACgkQBydwfqFbecxJOw//XaoJG3zN01bVM63H\nnFmMW/EnLzKrZqH8ZNq8CP9ycoc4q8SYcMprHKG9jufzj5/FhtpYecp3kBMpSYHt\nVu46LS9NajJDwdfvUMezVbieNIQ8icTR5s5IUYFlc47eG6PRe3k0n5fOPcIb6q82\nbyrK3dQnanOcVdoGU7QO9LAAHO9hg0zgZa0MxQAlDQov3dZcr7u7qGcQmU5JzcRS\nJgfDxHxDuMjmq6Kd0/UwD00kd2ptZgRls0ntXdm9CZGtQ/Q0baJ3eRzccpd/8bxy\nRWF9MnOdmV6ojcFKYECjEzcuheUlcKQH9rLkeBSfgrIlK3L7LG8bg5ouZLdx17rQ\nXABNQGmJTaGAiEnS/48G3roMS8R7fhUljcKr6t63QQQJ2qWdPvI6EMC2xKZsLHK4\nXiUvrmJpUprvEQSKBUOf/2zuXDBshtAnoKh7h5aG+TvozL4yNG5DKpSH3MRj1E43\nKoMsP/GN/X5h+vJnvhiCWxNMPP81Op0czBAgukBm627FTnsvieJOOrzyxb1s75+W\n56gJombmhzUfzr88AYY9mFy7diTw/oldDZcfwa8rvOAGJVDlyr2hqkLoGl+5jPex\nslt3NF4caE/wP9wPMgFRkmMOr8eiRhjlWLrO6mQdBp7Qsj3kEXioP+CZ1cv/sbaK\n4DM7VidB4PLrMFQMaf0LpjpC2DM=\n=wOl2\n-----END PGP PUBLIC KEY BLOCK-----", + "check_gpg": true + } + ], + "test_arch3": [ + { + "name": "test-distro", + "baseurl": "https://rpmrepo.osbuild.org/v2/mirror/public/f40/f40-ppc64le-rawhide-20240101", + "gpgkey": "-----BEGIN PGP PUBLIC KEY BLOCK-----\n\nmQINBGPQTCwBEADFUL0EQLzwpKHtlPkacVI156F2LnWp6K69g/6yzllidHI3b7EV\nQgQ9/Kdou6wNuOahNKa6WcEi6grEXexD7pAcu4xdRUp79XxQy5pC7Aq2/Dwf0vRL\n2y0kqof+C7iSzhHsfLoaqKKeh2njAo1KLZXYTHAWAMbXEyO/FJevaHLXe2+yYd7j\nluD58gyXgGDXXJ2lymLqs2jobjWdmGPNZGFl36RP3Dnk0FpbdH78kyIIsc2foYuF\n00rnuumwCtK3V58VOZo6IkaYk2irdyeetmJjVHwLHwJB3EaAwGy9Z2oAH3LxxFfk\nrQb0DH0Nzb3fpEziopOOqSi+6guV4RHUKAkCUMu+Mo5XwFVPUAIfNRTVqoIaEasC\nWO26lhkB87wwIvyb/TPGSeh6laHPRf0QOUOLkugdkSHoaJFWoTCcu9Y4aeDpf+ZQ\nfMVmkJNRS1tXONgz+pDk1rro/tNrkusYG18xjvSZTB0P0C4b4+jgK5l7me0NU6G3\nWw/hIng5lxWfXgE9bpxlN834v1xy5Z3v17guJu1ec/jzKzQQ4356wyegXURjYoWe\nawcnK1S+9gxivnkOk1bGLNxrEh5vB6PDcI1VQ1ECH50EHyvE1IXJDaaStdAkacv2\nqHcd15CnlBW1LYFj0CHs/sGu9FD0iSF95OVRX4gjg9Wa4f8KvtEO/f+FeQARAQAB\ntDFGZWRvcmEgKDQwKSA8ZmVkb3JhLTQwLXByaW1hcnlAZmVkb3JhcHJvamVjdC5v\ncmc+iQJOBBMBCAA4FiEEEV35rvhXhT7oRF0KBydwfqFbecwFAmPQTCwCGw8FCwkI\nBwIGFQoJCAsCBBYCAwECHgECF4AACgkQBydwfqFbecxJOw//XaoJG3zN01bVM63H\nnFmMW/EnLzKrZqH8ZNq8CP9ycoc4q8SYcMprHKG9jufzj5/FhtpYecp3kBMpSYHt\nVu46LS9NajJDwdfvUMezVbieNIQ8icTR5s5IUYFlc47eG6PRe3k0n5fOPcIb6q82\nbyrK3dQnanOcVdoGU7QO9LAAHO9hg0zgZa0MxQAlDQov3dZcr7u7qGcQmU5JzcRS\nJgfDxHxDuMjmq6Kd0/UwD00kd2ptZgRls0ntXdm9CZGtQ/Q0baJ3eRzccpd/8bxy\nRWF9MnOdmV6ojcFKYECjEzcuheUlcKQH9rLkeBSfgrIlK3L7LG8bg5ouZLdx17rQ\nXABNQGmJTaGAiEnS/48G3roMS8R7fhUljcKr6t63QQQJ2qWdPvI6EMC2xKZsLHK4\nXiUvrmJpUprvEQSKBUOf/2zuXDBshtAnoKh7h5aG+TvozL4yNG5DKpSH3MRj1E43\nKoMsP/GN/X5h+vJnvhiCWxNMPP81Op0czBAgukBm627FTnsvieJOOrzyxb1s75+W\n56gJombmhzUfzr88AYY9mFy7diTw/oldDZcfwa8rvOAGJVDlyr2hqkLoGl+5jPex\nslt3NF4caE/wP9wPMgFRkmMOr8eiRhjlWLrO6mQdBp7Qsj3kEXioP+CZ1cv/sbaK\n4DM7VidB4PLrMFQMaf0LpjpC2DM=\n=wOl2\n-----END PGP PUBLIC KEY BLOCK-----", + "check_gpg": true + } + ] +} From d39847638c69cc2f951f47b13c959e55bd3beb27 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Fri, 13 Sep 2024 14:54:17 -0700 Subject: [PATCH 7/7] cloudapi: Implement blueprint depsolving This converts the request's blueprint to an internal/blueprint, optionally selects the blueprint's distro and arch to override the host's and depsolves the blueprint. Also adds mock dnfjson depsolving to the test framework. And tests for the new behavior. Resolves: RHEL-60125 --- internal/cloudapi/v2/depsolve.go | 82 ++++++++++++++++++++++++ internal/cloudapi/v2/handler.go | 43 +++++++++++++ internal/cloudapi/v2/v2_internal_test.go | 54 ++++++++++++++++ internal/cloudapi/v2/v2_test.go | 78 ++++++++++++++++++++++ 4 files changed, 257 insertions(+) create mode 100644 internal/cloudapi/v2/depsolve.go diff --git a/internal/cloudapi/v2/depsolve.go b/internal/cloudapi/v2/depsolve.go new file mode 100644 index 0000000000..b1b8122a35 --- /dev/null +++ b/internal/cloudapi/v2/depsolve.go @@ -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 +} diff --git a/internal/cloudapi/v2/handler.go b/internal/cloudapi/v2/handler.go index 378bf55b0c..2e3641e368 100644 --- a/internal/cloudapi/v2/handler.go +++ b/internal/cloudapi/v2/handler.go @@ -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 +} diff --git a/internal/cloudapi/v2/v2_internal_test.go b/internal/cloudapi/v2/v2_internal_test.go index ed0949e6c6..7eafedf828 100644 --- a/internal/cloudapi/v2/v2_internal_test.go +++ b/internal/cloudapi/v2/v2_internal_test.go @@ -351,3 +351,57 @@ func TestStagesToPackageMetadata(t *testing.T) { assert.Equal(tc.pkgs, stagesToPackageMetadata(tc.stages), "mismatch in test case %d", idx) } } + +func TestPackageSpecToPackageMetadata(t *testing.T) { + assert := assert.New(t) + type testCase struct { + specs []rpmmd.PackageSpec + pkgs []PackageMetadata + } + testCases := []testCase{ + { + specs: []rpmmd.PackageSpec{ + { + Name: "vim-minimal", + Version: "8.0.1763", + Release: "15.el8", + Epoch: 2, + Arch: "x86_64", + Checksum: "sha256:HASH", + }, + { + Name: "unique", + Version: "1.90", + Release: "10", + Epoch: 0, + Arch: "aarch64", + Checksum: "sha256:HASH", + }, + }, + pkgs: []PackageMetadata{ + { + Type: "rpm", + Name: "vim-minimal", + Version: "8.0.1763", + Release: "15.el8", + Epoch: common.ToPtr("2"), + Arch: "x86_64", + Checksum: common.ToPtr("sha256:HASH"), + }, + { + Type: "rpm", + Name: "unique", + Version: "1.90", + Release: "10", + Epoch: nil, + Arch: "aarch64", + Checksum: common.ToPtr("sha256:HASH"), + }, + }, + }, + } + + for idx, tc := range testCases { + assert.Equal(tc.pkgs, packageSpecToPackageMetadata(tc.specs), "mismatch in test case %d", idx) + } +} diff --git a/internal/cloudapi/v2/v2_test.go b/internal/cloudapi/v2/v2_test.go index a4d0c73009..9b185203fb 100644 --- a/internal/cloudapi/v2/v2_test.go +++ b/internal/cloudapi/v2/v2_test.go @@ -5,6 +5,9 @@ import ( "encoding/json" "fmt" "net/http" + "os" + "os/exec" + "path/filepath" "sync" "testing" @@ -23,6 +26,7 @@ import ( "github.com/osbuild/images/pkg/sbom" v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2" "github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue" + mock_dnfjson "github.com/osbuild/osbuild-composer/internal/mocks/dnfjson" "github.com/osbuild/osbuild-composer/internal/target" "github.com/osbuild/osbuild-composer/internal/test" "github.com/osbuild/osbuild-composer/internal/worker" @@ -70,6 +74,23 @@ var sbomDoc = json.RawMessage(`{ ] }`) +var dnfjsonPath string + +// setupDNFJSON compiles the mock-dnf-json binary and sets the 'dnfjsonPath' variable +// to point to it for use with the Solver +func setupDNFJSON() { + // compile the mock-dnf-json binary to speed up tests + tmpdir, err := os.MkdirTemp("", "") + if err != nil { + panic(err) + } + dnfjsonPath = filepath.Join(tmpdir, "mock-dnf-json") + cmd := exec.Command("go", "build", "-o", dnfjsonPath, "../../../cmd/mock-dnf-json") + if err := cmd.Run(); err != nil { + panic(err) + } +} + func newV2Server(t *testing.T, dir string, depsolveChannels []string, enableJWT bool, failDepsolve bool) (*v2.Server, *worker.Server, jobqueue.JobQueue, context.CancelFunc) { q, err := fsjobqueue.New(dir) require.NoError(t, err) @@ -88,6 +109,10 @@ func newV2Server(t *testing.T, dir string, depsolveChannels []string, enableJWT solver := dnfjson.NewBaseSolver("") // test solver doesn't need a cache dir require.NotNil(t, solver) + // Just use the base mock dnfjson response file for these tests + respfile := mock_dnfjson.Base(dir) + solver.SetDNFJSONPath(dnfjsonPath, respfile) + config := v2.ServerConfig{ JWTEnabled: enableJWT, TenantProviderFields: []string{"rh-org-id", "account_id"}, @@ -1551,3 +1576,56 @@ func TestImageFromCompose(t *testing.T) { } }`, imgJobId, imgJobId)) } + +func TestDepsolveBlueprint(t *testing.T) { + srv, _, _, cancel := newV2Server(t, t.TempDir(), []string{""}, 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": "%s", + "architecture": "%s", + "packages": [ + { "name": "dep-package", "version": "*" } + ]} + }`, test_distro.TestDistro1Name, test_distro.TestArchName), + http.StatusOK, + `{ + "packages": [ + { + "name": "dep-package3", + "type": "rpm", + "epoch": "7", + "version": "3.0.3", + "release": "1.fc30", + "arch": "x86_64", + "checksum": "sha256:62278d360aa5045eb202af39fe85743a4b5615f0c9c7439a04d75d785db4c720" + }, { + "name": "dep-package1", + "type": "rpm", + "version": "1.33", + "release": "2.fc30", + "arch": "x86_64", + "checksum": "sha256:fe3951d112c3b1c84dc8eac57afe0830df72df1ca0096b842f4db5d781189893" + }, { + "name": "dep-package2", + "type": "rpm", + "version": "2.9", + "release": "1.fc30", + "arch": "x86_64", + "checksum": "sha256:5797c0b0489681596b5b3cd7165d49870b85b69d65e08770946380a3dcd49ea2" + } + ] + }`) +} + +// TestMain builds the mock dnf json binary and cleans it up on exit +func TestMain(m *testing.M) { + setupDNFJSON() + defer os.RemoveAll(dnfjsonPath) + code := m.Run() + os.Exit(code) +}