From 8f9451677984c58d5cbc5d687b957def092f1a9e Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 8 Jan 2025 18:15:20 +0100 Subject: [PATCH] main: add ostree integration This commit adds integration for the ostree options. It is modelled loosely after weldr-client/composer-cli and the ``` start-ostree --{ref,parent,url} ``` and uses ``` --ostree-{ref,parent,url} ``` A simple smoke test is provided that uses fedora-iot. Ideas welcome for an easier way :) --- cmd/image-builder/main.go | 35 +++++++++++++- cmd/image-builder/main_test.go | 87 ++++++++++++++++++++++++++++++++++ cmd/image-builder/manifest.go | 12 ++++- 3 files changed, 131 insertions(+), 3 deletions(-) diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index 41f5cbb..da24c3d 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -11,6 +11,7 @@ import ( "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/imagefilter" + "github.com/osbuild/images/pkg/ostree" "github.com/osbuild/image-builder-cli/internal/blueprintload" ) @@ -37,6 +38,31 @@ func cmdListImages(cmd *cobra.Command, args []string) error { return listImages(dataDir, output, filter) } +func ostreeImageOptions(cmd *cobra.Command) (*ostree.ImageOptions, error) { + imageRef, err := cmd.Flags().GetString("ostree-ref") + if err != nil { + return nil, err + } + parentRef, err := cmd.Flags().GetString("ostree-parent") + if err != nil { + return nil, err + } + url, err := cmd.Flags().GetString("ostree-url") + if err != nil { + return nil, err + } + if imageRef == "" && parentRef == "" && url == "" { + return nil, nil + } + + // XXX: how to add RHSM? + return &ostree.ImageOptions{ + ImageRef: imageRef, + ParentRef: parentRef, + URL: url, + }, nil +} + func cmdManifestWrapper(cmd *cobra.Command, args []string, w io.Writer, archChecker func(string) error) (*imagefilter.Result, error) { dataDir, err := cmd.Flags().GetString("datadir") if err != nil { @@ -53,6 +79,10 @@ func cmdManifestWrapper(cmd *cobra.Command, args []string, w io.Writer, archChec if err != nil { return nil, err } + ostreeImgOpts, err := ostreeImageOptions(cmd) + if err != nil { + return nil, err + } var blueprintPath string imgTypeStr := args[0] @@ -78,7 +108,7 @@ func cmdManifestWrapper(cmd *cobra.Command, args []string, w io.Writer, archChec } } - err = generateManifest(dataDir, blueprintPath, res, w) + err = generateManifest(dataDir, blueprintPath, res, w, ostreeImgOpts) return res, err } @@ -148,6 +178,9 @@ operating sytsems like centos and RHEL with easy customizations support.`, } manifestCmd.Flags().String("arch", "", `build manifest for a different architecture`) manifestCmd.Flags().String("distro", "", `build manifest for a different distroname (e.g. centos-9)`) + manifestCmd.Flags().String("ostree-ref", "", `OSTREE reference`) + manifestCmd.Flags().String("ostree-parent", "", `OSTREE parent`) + manifestCmd.Flags().String("ostree-url", "", `OSTREE url`) rootCmd.AddCommand(manifestCmd) buildCmd := &cobra.Command{ diff --git a/cmd/image-builder/main_test.go b/cmd/image-builder/main_test.go index ec745c3..c88c3b7 100644 --- a/cmd/image-builder/main_test.go +++ b/cmd/image-builder/main_test.go @@ -3,9 +3,12 @@ package main_test import ( "bytes" "encoding/json" + "io" + "net/http" "os" "path/filepath" "slices" + "strings" "testing" "github.com/sirupsen/logrus" @@ -161,6 +164,7 @@ func TestManifestIntegrationSmoke(t *testing.T) { restore = main.MockOsArgs([]string{ "manifest", "qcow2", + "--arch=x86_64", "--distro=centos-9", makeTestBlueprint(t, testBlueprint), }) @@ -216,6 +220,89 @@ func TestManifestIntegrationCrossArch(t *testing.T) { assert.Contains(t, fakeStdout.String(), `.el9.s390x.rpm`) } +func TestManifestIntegrationOstreeSmoke(t *testing.T) { + if testing.Short() { + t.Skip("manifest generation takes a while") + } + if !hasDepsolveDnf() { + t.Skip("no osbuild-depsolve-dnf binary found") + } + + restore := main.MockNewRepoRegistry(testrepos.New) + defer restore() + + // we cannot hit ostree.f.o directly, we need to go via the mirrorlist + resp, err := http.Get("https://ostree.fedoraproject.org/iot/mirrorlist") + assert.NoError(t, err) + defer resp.Body.Close() + assert.Equal(t, http.StatusOK, resp.StatusCode) + body, err := io.ReadAll(resp.Body) + assert.NoError(t, err) + restore = main.MockOsArgs([]string{ + "manifest", + "iot-raw-image", + "--arch=x86_64", + "--distro=fedora-40", + "--ostree-url=" + strings.SplitN(string(body), "\n", 2)[0], + "--ostree-ref=fedora/stable/x86_64/iot", + }) + defer restore() + + var fakeStdout bytes.Buffer + restore = main.MockOsStdout(&fakeStdout) + defer restore() + + err = main.Run() + assert.NoError(t, err) + + pipelineNames, err := manifesttest.PipelineNamesFrom(fakeStdout.Bytes()) + assert.NoError(t, err) + assert.Contains(t, pipelineNames, "ostree-deployment") + + // XXX: provide helpers in manifesttest to extract this in a nicer way + assert.Contains(t, fakeStdout.String(), `{"type":"org.osbuild.ostree.init-fs"`) +} + +func TestManifestIntegrationOstreeSmokeErrors(t *testing.T) { + if testing.Short() { + t.Skip("manifest generation takes a while") + } + + restore := main.MockNewRepoRegistry(testrepos.New) + defer restore() + + baseArgs := []string{ + "manifest", + "--arch=x86_64", + "--distro=fedora-40", + } + + for _, tc := range []struct { + extraArgs []string + expectedErr string + }{ + { + []string{"iot-raw-image"}, + `iot-raw-image: ostree commit URL required`, + }, + { + []string{"qcow2", "--ostree-url=http://example.com/"}, + `OSTree is not supported for "qcow2"`, + }, + } { + args := append(baseArgs, tc.extraArgs...) + restore = main.MockOsArgs(args) + defer restore() + + var fakeStdout bytes.Buffer + restore = main.MockOsStdout(&fakeStdout) + defer restore() + + err := main.Run() + assert.EqualError(t, err, tc.expectedErr) + } +} + func TestBuildIntegrationHappy(t *testing.T) { if testing.Short() { t.Skip("manifest generation takes a while") diff --git a/cmd/image-builder/manifest.go b/cmd/image-builder/manifest.go index fb6ee2c..d5de84b 100644 --- a/cmd/image-builder/manifest.go +++ b/cmd/image-builder/manifest.go @@ -3,13 +3,15 @@ package main import ( "io" + "github.com/osbuild/images/pkg/distro" "github.com/osbuild/images/pkg/imagefilter" + "github.com/osbuild/images/pkg/ostree" "github.com/osbuild/image-builder-cli/internal/blueprintload" "github.com/osbuild/image-builder-cli/internal/manifestgen" ) -func generateManifest(dataDir, blueprintPath string, res *imagefilter.Result, output io.Writer) error { +func generateManifest(dataDir, blueprintPath string, res *imagefilter.Result, output io.Writer, ostreeOpts *ostree.ImageOptions) error { repos, err := newRepoRegistry(dataDir) if err != nil { return err @@ -25,6 +27,12 @@ func generateManifest(dataDir, blueprintPath string, res *imagefilter.Result, ou if err != nil { return err } + var imgOpts *distro.ImageOptions + if ostreeOpts != nil { + imgOpts = &distro.ImageOptions{ + OSTree: ostreeOpts, + } + } - return mg.Generate(bp, res.Distro, res.ImgType, res.Arch, nil) + return mg.Generate(bp, res.Distro, res.ImgType, res.Arch, imgOpts) }