osbuild-playground: introduce new tool

This is meant for rapid prototyping of single image types and for
osbuild development, as an alternative to osbuild-mpp. The same
primitives are used as in the image definitions, but without any
policy or inheritance applied.

The user is expected to only edit `playground.go` and then run
the tool to produce osbuild manifests.
This commit is contained in:
Tom Gundersen 2022-06-29 22:35:26 +01:00
parent a199745796
commit ff1451d8ce
2 changed files with 159 additions and 0 deletions

View file

@ -0,0 +1,116 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io"
"io/ioutil"
"os"
"path"
"github.com/osbuild/osbuild-composer/internal/common"
"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"
)
// 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
// version if run from within the source tree.
func findDnfJsonBin() string {
locations := []string{"./dnf-json", "/usr/libexec/osbuild-composer/dnf-json", "/usr/lib/osbuild-composer/dnf-json"}
for _, djPath := range locations {
_, err := os.Stat(djPath)
if !os.IsNotExist(err) {
return djPath
}
}
// can't run: panic
panic(fmt.Sprintf("could not find 'dnf-json' in any of the known paths: %+v", locations))
}
func main() {
// Path to MyOptions or '-' for stdin
myOptionsArg := flag.Arg(0)
myOptions := &MyOptions{}
if myOptionsArg != "" {
var reader io.Reader
if myOptionsArg == "-" {
reader = os.Stdin
} else {
var err error
reader, err = os.Open(myOptionsArg)
if err != nil {
panic("Could not open path to image options: " + err.Error())
}
}
file, err := ioutil.ReadAll(reader)
if err != nil {
panic("Could not read image options: " + err.Error())
}
err = json.Unmarshal(file, &myOptions)
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")
}
arch, err := d.GetArch(common.CurrentArch())
if err != nil {
panic("host arch not supported")
}
repos, err := rpmmd.LoadRepositories([]string{"./"}, d.Name())
if err != nil {
panic("could not load repositories for distro " + d.Name())
}
home, err := os.UserHomeDir()
if err != nil {
panic("os.UserHomeDir(): " + err.Error())
}
solver := dnfjson.NewSolver(d.ModulePlatformID(), d.Releasever(), arch.Name(), path.Join(home, ".cache/osbuild-playground/rpmmd"))
solver.SetDNFJSONPath(findDnfJsonBin())
// 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())
}
}

View file

@ -0,0 +1,43 @@
package main
import (
"github.com/osbuild/osbuild-composer/internal/manifest"
"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"`
}
// 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!
// 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, false, "", "", repos, nil, manifest.BOOTLOADER_GRUB, "", "")
os.ExtraBasePackages = []string{
"@core",
}
// create an OCI container containing the OS tree created above
manifest.NewOCIContainerPipeline(m, build, &os.BasePipeline, "x86_64", "my-container.tar")
return nil
}