From edc3d8a00448edd7a03d9b7df97555c4512fa52f Mon Sep 17 00:00:00 2001 From: Michael Vogt Date: Wed, 30 Oct 2024 18:38:02 +0100 Subject: [PATCH] cli: implement messy/untested "image-builder manifest" --- cmd/image-builder/list_images.go | 19 +++++-- cmd/image-builder/main.go | 27 ++++++++- cmd/image-builder/manifest.go | 94 ++++++++++++++++++++++++++++++++ 3 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 cmd/image-builder/manifest.go diff --git a/cmd/image-builder/list_images.go b/cmd/image-builder/list_images.go index edba8c2554..18892747ea 100644 --- a/cmd/image-builder/list_images.go +++ b/cmd/image-builder/list_images.go @@ -17,23 +17,32 @@ var repositoryConfigs = []string{ "/usr/share/osbuild-composer", } -func listImages(out io.Writer, format string, filterExprs []string) error { +// XXX: move to pkg/reporegistry +func newRepoRegistry() (*reporegistry.RepoRegistry, error) { // useful for development/debugging, e.g. run: // go build && IMAGE_BUILDER_EXTRA_REPOS_PATH=../../test/data ./image-builder if extraReposPath := os.Getenv("IMAGE_BUILDER_EXTRA_REPOS_PATH"); extraReposPath != "" { repositoryConfigs = append(repositoryConfigs, strings.Split(extraReposPath, ":")...) } - repos, err := reporegistry.New(repositoryConfigs) + return reporegistry.New(repositoryConfigs) +} + +func getFilteredImages(filterExprs []string) ([]FilterResult, error) { + repos, err := newRepoRegistry() if err != nil { - return err + return nil, err } filters, err := NewFilters(filterExprs) if err != nil { - return err + return nil, err } fac := distrofactory.NewDefault() - filteredResult, err := FilterDistros(fac, repos.ListDistros(), filters) + return FilterDistros(fac, repos.ListDistros(), filters) +} + +func listImages(out io.Writer, format string, filterExprs []string) error { + filteredResult, err := getFilteredImages(filterExprs) if err != nil { return err } diff --git a/cmd/image-builder/main.go b/cmd/image-builder/main.go index f8ffa57537..dfed369cdf 100644 --- a/cmd/image-builder/main.go +++ b/cmd/image-builder/main.go @@ -6,8 +6,9 @@ import ( "os" "github.com/sirupsen/logrus" - "github.com/spf13/cobra" + + "github.com/osbuild/images/pkg/arch" ) var osStdout io.Writer = os.Stdout @@ -25,6 +26,19 @@ func cmdListImages(cmd *cobra.Command, args []string) error { return listImages(osStdout, format, filter) } +func cmdManifest(cmd *cobra.Command, args []string) error { + distroName := args[0] + imgType := args[1] + var archStr string + if len(args) > 2 { + archStr = args[2] + } else { + archStr = arch.Current().String() + } + + return outputManifest(osStdout, distroName, imgType, archStr) +} + func run() error { // images logs a bunch of stuff to Debug/Info that we we do not // want to show @@ -44,6 +58,7 @@ operating sytsems like centos and RHEL with easy customizations support.`, // distro names? listImagesCmd := &cobra.Command{ Use: "list-images", + Short: "List buildable images, use --filter to limit further", RunE: cmdListImages, SilenceUsage: true, } @@ -51,6 +66,16 @@ operating sytsems like centos and RHEL with easy customizations support.`, listImagesCmd.Flags().String("format", "", "Output in a specific format (text,json)") rootCmd.AddCommand(listImagesCmd) + manifestCmd := &cobra.Command{ + Use: "manifest []", + Short: "Build manifest for the given distro/image-type, e.g. centos-9 qcow2", + RunE: cmdManifest, + SilenceUsage: true, + Args: cobra.MinimumNArgs(2), + Hidden: true, + } + rootCmd.AddCommand(manifestCmd) + return rootCmd.Execute() } diff --git a/cmd/image-builder/manifest.go b/cmd/image-builder/manifest.go new file mode 100644 index 0000000000..5f2c22308a --- /dev/null +++ b/cmd/image-builder/manifest.go @@ -0,0 +1,94 @@ +package main + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/osbuild/images/pkg/blueprint" + "github.com/osbuild/images/pkg/distro" + "github.com/osbuild/images/pkg/dnfjson" + "github.com/osbuild/images/pkg/rpmmd" + "github.com/osbuild/images/pkg/sbom" +) + +// XXX: duplicated from cmd/build/main.go:depsolve (and probably more places) +func depsolve(cacheDir string, packageSets map[string][]rpmmd.PackageSet, d distro.Distro, arch string) (map[string][]rpmmd.PackageSpec, map[string][]rpmmd.RepoConfig, error) { + solver := dnfjson.NewSolver(d.ModulePlatformID(), d.Releasever(), arch, d.Name(), cacheDir) + depsolvedSets := make(map[string][]rpmmd.PackageSpec) + repoSets := make(map[string][]rpmmd.RepoConfig) + for name, pkgSet := range packageSets { + res, err := solver.Depsolve(pkgSet, sbom.StandardTypeNone) + if err != nil { + return nil, nil, err + } + depsolvedSets[name] = res.Packages + repoSets[name] = res.Repos + } + return depsolvedSets, repoSets, nil +} + +func outputManifest(out io.Writer, distroName, imgTypeStr, archStr string) error { + filterExprs := []string{ + fmt.Sprintf("name:%s", distroName), + fmt.Sprintf("arch:%s", archStr), + fmt.Sprintf("type:%s", imgTypeStr), + } + filteredResults, err := getFilteredImages(filterExprs) + if err != nil { + return err + } + switch len(filteredResults) { + case 0: + return fmt.Errorf("cannot find image for %s %s %s", distroName, imgTypeStr, archStr) + case 1: + // nothing + default: + return fmt.Errorf("internal error: found %v results for %s %s %s", len(filteredResults), distroName, imgTypeStr, archStr) + } + + var bp blueprint.Blueprint + // XXX: what/how much do we expose here? + options := distro.ImageOptions{} + distro := filteredResults[0].Distro + imgType := filteredResults[0].ImgType + + reporeg, err := newRepoRegistry() + if err != nil { + return err + } + repos, err := reporeg.ReposByImageTypeName(distroName, archStr, imgTypeStr) + if err != nil { + return err + } + preManifest, warnings, err := imgType.Manifest(&bp, options, repos, 0) + if err != nil { + return err + } + if len(warnings) > 0 { + // XXX: what can we do here? for things like json output? + // what are these warnings? + return fmt.Errorf("warnings during manifest creation: %v", strings.Join(warnings, "\n")) + } + // XXX: cleanup, use shared dir,etc + cacheDir, err := os.MkdirTemp("", "depsolve") + if err != nil { + return err + } + packageSpecs, _, err := depsolve(cacheDir, preManifest.GetPackageSetChains(), distro, archStr) + if err != nil { + return err + } + if packageSpecs == nil { + return fmt.Errorf("depsolve did not return any packages") + } + // XXX: support commit/container specs + mf, err := preManifest.Serialize(packageSpecs, nil, nil, nil) + if err != nil { + return err + } + fmt.Fprintf(out, "%s\n", mf) + + return nil +}