diff --git a/cmd/osbuild-playground/main.go b/cmd/osbuild-playground/main.go index a7939b1b5..319f2fe0d 100644 --- a/cmd/osbuild-playground/main.go +++ b/cmd/osbuild-playground/main.go @@ -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) } diff --git a/cmd/osbuild-playground/my-image.go b/cmd/osbuild-playground/my-image.go new file mode 100644 index 000000000..bfcd36854 --- /dev/null +++ b/cmd/osbuild-playground/my-image.go @@ -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"} +} diff --git a/cmd/osbuild-playground/playground.go b/cmd/osbuild-playground/playground.go index cf2379cd1..45d2a9e65 100644 --- a/cmd/osbuild-playground/playground.go +++ b/cmd/osbuild-playground/playground.go @@ -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()) + } }