osbuild-playground: rework slightly

Invoke osbuild, rather than output the manifest. Make it easier to include
several image types.
This commit is contained in:
Tom Gundersen 2022-07-08 00:40:10 +01:00
parent 1b924ae30c
commit 33fe2da25c
3 changed files with 163 additions and 75 deletions

View file

@ -10,12 +10,25 @@ import (
"path"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/osbuild-composer/internal/dnfjson"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
var ImageTypes = make(map[string]ImageType)
type ImageType interface {
Name() string
InstantiateManifest(m *manifest.Manifest, repos []rpmmd.RepoConfig, runner string) error
GetExports() []string
GetCheckpoints() []string
}
func AddImageType(img ImageType) {
ImageTypes[img.Name()] = img
}
// osbuild-playground is a utility command and is often run from within the
// source tree. Find the dnf-json binary in case the osbuild-composer package
// isn't installed. This prioritises the local source version over the system
@ -34,17 +47,25 @@ func findDnfJsonBin() string {
}
func main() {
// Path to MyOptions or '-' for stdin
myOptionsArg := flag.Arg(0)
var distroArg string
flag.StringVar(&distroArg, "distro", "", "distro to build from")
var archArg string
flag.StringVar(&archArg, "arch", common.CurrentArch(), "architecture to build for")
var imageTypeArg string
flag.StringVar(&imageTypeArg, "type", "my-image", "image type to build")
flag.Parse()
myOptions := &MyOptions{}
if myOptionsArg != "" {
// Path to options or '-' for stdin
optionsArg := flag.Arg(0)
img := ImageTypes[imageTypeArg]
if optionsArg != "" {
var reader io.Reader
if myOptionsArg == "-" {
if optionsArg == "-" {
reader = os.Stdin
} else {
var err error
reader, err = os.Open(myOptionsArg)
reader, err = os.Open(optionsArg)
if err != nil {
panic("Could not open path to image options: " + err.Error())
}
@ -53,21 +74,29 @@ func main() {
if err != nil {
panic("Could not read image options: " + err.Error())
}
err = json.Unmarshal(file, &myOptions)
err = json.Unmarshal(file, img)
if err != nil {
panic("Could not parse image options: " + err.Error())
}
}
distros := distroregistry.NewDefault()
d := distros.FromHost()
if d == nil {
panic("host distro not supported")
var d distro.Distro
if distroArg != "" {
d = distros.GetDistro(distroArg)
if d == nil {
panic(fmt.Sprintf("distro '%s' not supported\n", distroArg))
}
} else {
d = distros.FromHost()
if d == nil {
panic("host distro not supported")
}
}
arch, err := d.GetArch(common.CurrentArch())
arch, err := d.GetArch(archArg)
if err != nil {
panic("host arch not supported")
panic(fmt.Sprintf("arch '%s' not supported\n", archArg))
}
repos, err := rpmmd.LoadRepositories([]string{"./"}, d.Name())
@ -80,37 +109,7 @@ func main() {
panic("os.UserHomeDir(): " + err.Error())
}
solver := dnfjson.NewSolver(d.ModulePlatformID(), d.Releasever(), arch.Name(), path.Join(home, ".cache/osbuild-playground/rpmmd"))
solver.SetDNFJSONPath(findDnfJsonBin())
state_dir := path.Join(home, ".local/share/osbuild-playground/")
// Set cache size to 3 GiB
solver.SetMaxCacheSize(1 * 1024 * 1024 * 1024)
manifest := manifest.New()
// TODO: figure out the runner situation
err = MyManifest(&manifest, myOptions, repos[arch.Name()], "org.osbuild.fedora36")
if err != nil {
panic("MyManifest() failed: " + err.Error())
}
packageSpecs := make(map[string][]rpmmd.PackageSpec)
for name, chain := range manifest.GetPackageSetChains() {
packages, err := solver.Depsolve(chain)
if err != nil {
panic(fmt.Sprintf("failed to depsolve for pipeline %s: %s\n", name, err.Error()))
}
packageSpecs[name] = packages
}
bytes, err := manifest.Serialize(packageSpecs)
if err != nil {
panic("failed to serialize manifest: " + err.Error())
}
os.Stdout.Write(bytes)
if err := solver.CleanCache(); err != nil {
// print to stderr but don't exit with error
fmt.Fprintf(os.Stderr, "could not clean dnf cache: %s", err.Error())
}
RunPlayground(img, d, arch, repos, state_dir)
}

View file

@ -0,0 +1,77 @@
package main
import (
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
func init() {
AddImageType(&MyImage{})
}
// MyImage contains the arguments passed in as a JSON blob.
// You can replace them with whatever you want to use to
// configure your image. In the current example they are
// unused.
type MyImage struct {
MyOption string `json:"my_option"`
Filename string `json:"filename"`
}
// Name returns the name of the image type, used to select what kind
// of image to build.
func (img *MyImage) Name() string {
return "my-image"
}
// Build your manifest by attaching pipelines to it
//
// @m is the manifest you are constructing
// @options are what was passed in on the commandline
// @repos are the default repositories for the host OS/arch
// @runner is needed by any build pipelines
//
// Return nil when you are done, or an error if something
// went wrong. Your manifest will be streamed to osbuild
// for building.
func (img *MyImage) InstantiateManifest(m *manifest.Manifest, repos []rpmmd.RepoConfig, runner string) error {
// Let's create a simple OCI container!
// configure a build pipeline
build := manifest.NewBuildPipeline(m, runner, repos)
// create a non-bootable OS tree containing the `core` comps group
os := manifest.NewOSPipeline(m, build, &platform.X86{}, repos)
os.ExtraBasePackages = []string{
"@core",
}
filename := "my-container.tar"
if img.Filename != "" {
filename = img.Filename
}
// create an OCI container containing the OS tree created above
manifest.NewOCIContainerPipeline(m, build, &os.BasePipeline, "x86_64", filename)
return nil
}
// GetExports returns a list of the pipelines osbuild should export.
// These are the pipelines containing the artefact you want returned.
//
// TODO: Move this to be implemented in terms ofthe Manifest package.
// We should not need to know the pipeline names.
func (img *MyImage) GetExports() []string {
return []string{"container"}
}
// GetCheckpoints returns a list of the pipelines osbuild should
// checkpoint. These are the pipelines likely to be reusable in
// future runs.
//
// TODO: Move this to be implemented in terms ofthe Manifest package.
// We should not need to know the pipeline names.
func (img *MyImage) GetCheckpoints() []string {
return []string{"build"}
}

View file

@ -1,44 +1,56 @@
package main
import (
"fmt"
"os"
"path"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/dnfjson"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/osbuild2"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
// MyOptions contains the arguments passed in as a JSON blob.
// You can replace them with whatever you want to use to
// configure your image. In the current example they are
// unused.
type MyOptions struct {
MyOption string `json:"my_option"`
}
func RunPlayground(img ImageType, d distro.Distro, arch distro.Arch, repos map[string][]rpmmd.RepoConfig, state_dir string) {
// Build your manifest by attaching pipelines to it
//
// @m is the manifest you are constructing
// @options are what was passed in on the commandline
// @repos are the default repositories for the host OS/arch
// @runner is needed by any build pipelines
//
// Return nil when you are done, or an error if something
// went wrong. Your manifest will be streamed to stdout and
// can be piped directly to either jq for inspection or
// osbuild for building.
func MyManifest(m *manifest.Manifest, options *MyOptions, repos []rpmmd.RepoConfig, runner string) error {
// Let's create a simple OCI container!
solver := dnfjson.NewSolver(d.ModulePlatformID(), d.Releasever(), arch.Name(), path.Join(state_dir, "rpmmd"))
solver.SetDNFJSONPath(findDnfJsonBin())
// configure a build pipeline
build := manifest.NewBuildPipeline(m, runner, repos)
// Set cache size to 3 GiB
solver.SetMaxCacheSize(1 * 1024 * 1024 * 1024)
// create a non-bootable OS tree containing the `core` comps group
os := manifest.NewOSPipeline(m, build, &platform.X86{}, repos)
os.ExtraBasePackages = []string{
"@core",
manifest := manifest.New()
// TODO: figure out the runner situation
err := img.InstantiateManifest(&manifest, repos[arch.Name()], "org.osbuild.fedora36")
if err != nil {
panic("InstantiateManifest() failed: " + err.Error())
}
// create an OCI container containing the OS tree created above
manifest.NewOCIContainerPipeline(m, build, &os.BasePipeline, "x86_64", "my-container.tar")
packageSpecs := make(map[string][]rpmmd.PackageSpec)
for name, chain := range manifest.GetPackageSetChains() {
packages, err := solver.Depsolve(chain)
if err != nil {
panic(fmt.Sprintf("failed to depsolve for pipeline %s: %s\n", name, err.Error()))
}
packageSpecs[name] = packages
}
return nil
if err := solver.CleanCache(); err != nil {
// print to stderr but don't exit with error
fmt.Fprintf(os.Stderr, "could not clean dnf cache: %s", err.Error())
}
bytes, err := manifest.Serialize(packageSpecs)
if err != nil {
panic("failed to serialize manifest: " + err.Error())
}
store := path.Join(state_dir, "osbuild-store")
_, err = osbuild2.RunOSBuild(bytes, store, "./", img.GetExports(), []string{"build"}, false, os.Stdout)
if err != nil {
fmt.Fprintf(os.Stderr, "could not run osbuild: %s", err.Error())
}
}