diff --git a/cmd/osbuild-playground/main.go b/cmd/osbuild-playground/main.go new file mode 100644 index 000000000..a7939b1b5 --- /dev/null +++ b/cmd/osbuild-playground/main.go @@ -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()) + } +} diff --git a/cmd/osbuild-playground/playground.go b/cmd/osbuild-playground/playground.go new file mode 100644 index 000000000..b9acbc024 --- /dev/null +++ b/cmd/osbuild-playground/playground.go @@ -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 +}