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 :)
This commit is contained in:
Michael Vogt 2025-01-08 18:15:20 +01:00 committed by Simon de Vlieger
parent 00e18fe1cd
commit 8f94516779
3 changed files with 131 additions and 3 deletions

View file

@ -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{

View file

@ -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")

View file

@ -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)
}