From b134feb17ce6402658be07d48dd7142881d9062d Mon Sep 17 00:00:00 2001 From: Jakub Ciolek Date: Mon, 14 Aug 2023 10:41:34 +0200 Subject: [PATCH] refactor(packmanager): streamline package manager initialization Create a singleton for the umbrella package manager and use it instead of global package-scoped maps. Use more dependency injection. Move the scattered init() calls to RegisterPackageManager into one place. Create a bootstrap package for handling flag/zip registration. Use the bootstrapping package in main(). Propagate errors up and report them if initialization fails. This should make things a bit more testable in the future. GitHub-Fixes: #425 Signed-off-by: Jakub Ciolek --- api/api.go | 11 +- cmd/kraft/build/build.go | 5 +- cmd/kraft/clean/clean.go | 5 +- cmd/kraft/fetch/fetch.go | 5 +- cmd/kraft/kraft.go | 6 + cmd/kraft/menu/menu.go | 5 +- cmd/kraft/pkg/list/list.go | 5 +- cmd/kraft/pkg/pkg.go | 5 +- cmd/kraft/pkg/pull/pull.go | 5 +- cmd/kraft/pkg/push/push.go | 11 +- cmd/kraft/pkg/source/source.go | 5 +- cmd/kraft/pkg/unsource/unsource.go | 5 +- cmd/kraft/pkg/update/update.go | 5 +- cmd/kraft/prepare/prepare.go | 5 +- cmd/kraft/properclean/properclean.go | 5 +- cmd/kraft/run/run.go | 14 +- cmd/kraft/run/runner.go | 20 ++- cmd/kraft/set/set.go | 5 +- cmd/kraft/unset/unset.go | 5 +- internal/bootstrap/init.go | 50 +++++++ machine/qemu/init.go | 2 + manifest/init.go | 10 +- oci/init.go | 20 +-- packmanager/context.go | 2 +- packmanager/umbrella.go | 196 +++++++++++++++++---------- tools/github-action/main.go | 10 +- unikraft/elfloader/init.go | 2 +- 27 files changed, 277 insertions(+), 147 deletions(-) create mode 100644 internal/bootstrap/init.go diff --git a/api/api.go b/api/api.go index c5508f319..e42af29ef 100644 --- a/api/api.go +++ b/api/api.go @@ -9,7 +9,6 @@ import ( zip "api.zip" "k8s.io/apimachinery/pkg/api/resource" - utilruntime "k8s.io/apimachinery/pkg/util/runtime" machinev1alpha1 "kraftkit.sh/api/machine/v1alpha1" networkv1alpha1 "kraftkit.sh/api/network/v1alpha1" @@ -17,11 +16,13 @@ import ( ) func init() { - utilruntime.Must(zip.Register( + gob.Register(resource.Quantity{}) +} + +func RegisterSchemes() error { + return zip.Register( machinev1alpha1.AddToScheme, networkv1alpha1.AddToScheme, volumev1alpha1.AddToScheme, - )) - - gob.Register(resource.Quantity{}) + ) } diff --git a/cmd/kraft/build/build.go b/cmd/kraft/build/build.go index e3d79d441..4e683bdb7 100644 --- a/cmd/kraft/build/build.go +++ b/cmd/kraft/build/build.go @@ -82,13 +82,12 @@ func New() *cobra.Command { } func (opts *Build) Pre(cmd *cobra.Command, args []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) if len(args) == 0 { opts.workdir, err = os.Getwd() diff --git a/cmd/kraft/clean/clean.go b/cmd/kraft/clean/clean.go index 46555ff02..22d0f6671 100644 --- a/cmd/kraft/clean/clean.go +++ b/cmd/kraft/clean/clean.go @@ -76,13 +76,12 @@ func New() *cobra.Command { } func (opts *Clean) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) opts.Platform = platform.PlatformByName(opts.Platform).String() diff --git a/cmd/kraft/fetch/fetch.go b/cmd/kraft/fetch/fetch.go index 30088de5f..a8dd7d99d 100644 --- a/cmd/kraft/fetch/fetch.go +++ b/cmd/kraft/fetch/fetch.go @@ -52,13 +52,12 @@ func New() *cobra.Command { } func (opts *Fetch) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) opts.Platform = platform.PlatformByName(opts.Platform).String() diff --git a/cmd/kraft/kraft.go b/cmd/kraft/kraft.go index cf04a96b6..992513652 100644 --- a/cmd/kraft/kraft.go +++ b/cmd/kraft/kraft.go @@ -14,6 +14,7 @@ import ( "kraftkit.sh/cmdfactory" "kraftkit.sh/config" + "kraftkit.sh/internal/bootstrap" "kraftkit.sh/internal/cli" kitupdate "kraftkit.sh/internal/update" kitversion "kraftkit.sh/internal/version" @@ -140,5 +141,10 @@ func main() { } } + if err := bootstrap.InitKraftkit(ctx); err != nil { + log.G(ctx).Errorf("could not init kraftkit: %v", err) + os.Exit(1) + } + cmdfactory.Main(ctx, cmd) } diff --git a/cmd/kraft/menu/menu.go b/cmd/kraft/menu/menu.go index d215c04d0..91551eb21 100644 --- a/cmd/kraft/menu/menu.go +++ b/cmd/kraft/menu/menu.go @@ -55,13 +55,12 @@ func New() *cobra.Command { } func (opts *Menu) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) opts.Platform = platform.PlatformByName(opts.Platform).String() diff --git a/cmd/kraft/pkg/list/list.go b/cmd/kraft/pkg/list/list.go index eb6c22233..caf60a376 100644 --- a/cmd/kraft/pkg/list/list.go +++ b/cmd/kraft/pkg/list/list.go @@ -58,13 +58,12 @@ func New() *cobra.Command { } func (*List) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/cmd/kraft/pkg/pkg.go b/cmd/kraft/pkg/pkg.go index 348024c5e..2132cdd47 100644 --- a/cmd/kraft/pkg/pkg.go +++ b/cmd/kraft/pkg/pkg.go @@ -90,13 +90,12 @@ func (opts *Pkg) Pre(cmd *cobra.Command, _ []string) error { return fmt.Errorf("the `--arch` and `--plat` options are not supported in addition to `--target`") } - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) opts.Platform = platform.PlatformByName(opts.Platform).String() diff --git a/cmd/kraft/pkg/pull/pull.go b/cmd/kraft/pkg/pull/pull.go index f0eac4bca..2e61630e1 100644 --- a/cmd/kraft/pkg/pull/pull.go +++ b/cmd/kraft/pkg/pull/pull.go @@ -70,13 +70,12 @@ func New() *cobra.Command { } func (opts *Pull) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) opts.Platform = platform.PlatformByName(opts.Platform).String() diff --git a/cmd/kraft/pkg/push/push.go b/cmd/kraft/pkg/push/push.go index 61aad5c5f..19f85542d 100644 --- a/cmd/kraft/pkg/push/push.go +++ b/cmd/kraft/pkg/push/push.go @@ -56,13 +56,12 @@ func New() *cobra.Command { } func (opts *Push) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } @@ -111,7 +110,11 @@ func (opts *Push) Run(cmd *cobra.Command, args []string) error { var pmananger packmanager.PackageManager if opts.Format != "auto" { - pmananger = packmanager.PackageManagers()[pack.PackageFormat(opts.Format)] + umbrella, err := packmanager.PackageManagers() + if err != nil { + return err + } + pmananger = umbrella[pack.PackageFormat(opts.Format)] if pmananger == nil { return errors.New("invalid package format specified") } diff --git a/cmd/kraft/pkg/source/source.go b/cmd/kraft/pkg/source/source.go index 7aab5c0df..0d0e66e8e 100644 --- a/cmd/kraft/pkg/source/source.go +++ b/cmd/kraft/pkg/source/source.go @@ -44,13 +44,12 @@ func New() *cobra.Command { } func (*Source) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/cmd/kraft/pkg/unsource/unsource.go b/cmd/kraft/pkg/unsource/unsource.go index acff5bb33..5be1c7c16 100644 --- a/cmd/kraft/pkg/unsource/unsource.go +++ b/cmd/kraft/pkg/unsource/unsource.go @@ -43,13 +43,12 @@ func New() *cobra.Command { } func (*Unsource) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/cmd/kraft/pkg/update/update.go b/cmd/kraft/pkg/update/update.go index be676dcf6..2e9a7ff54 100644 --- a/cmd/kraft/pkg/update/update.go +++ b/cmd/kraft/pkg/update/update.go @@ -44,13 +44,12 @@ func New() *cobra.Command { } func (*Update) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/cmd/kraft/prepare/prepare.go b/cmd/kraft/prepare/prepare.go index 0c303548a..d43e75864 100644 --- a/cmd/kraft/prepare/prepare.go +++ b/cmd/kraft/prepare/prepare.go @@ -52,13 +52,12 @@ func New() *cobra.Command { } func (opts *Prepare) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) opts.Platform = platform.PlatformByName(opts.Platform).String() diff --git a/cmd/kraft/properclean/properclean.go b/cmd/kraft/properclean/properclean.go index fadf7cd29..3d5bd49f5 100644 --- a/cmd/kraft/properclean/properclean.go +++ b/cmd/kraft/properclean/properclean.go @@ -72,13 +72,12 @@ func New() *cobra.Command { } func (*ProperClean) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/cmd/kraft/run/run.go b/cmd/kraft/run/run.go index 5641b0125..98865cab7 100644 --- a/cmd/kraft/run/run.go +++ b/cmd/kraft/run/run.go @@ -187,16 +187,19 @@ func (opts *Run) Pre(cmd *cobra.Command, _ []string) error { if opts.RunAs == "" || !set.NewStringSet("kernel", "project").Contains(opts.RunAs) { // Set use of the global package manager. - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) } if opts.RunAs != "" { - runners := runnersByName() + runners, err := runnersByName() + if err != nil { + return err + } if _, ok = runners[opts.RunAs]; !ok { choices := make([]string, len(runners)) i := 0 @@ -229,7 +232,10 @@ func (opts *Run) Run(cmd *cobra.Command, args []string) error { var run runner var errs []error - runners := runners() + runners, err := runners() + if err != nil { + return err + } // Iterate through the list of built-in runners which sequentially tests and // first test whether the --as flag has been set to force a specific runner or diff --git a/cmd/kraft/run/runner.go b/cmd/kraft/run/runner.go index c806ef20b..c60d925ae 100644 --- a/cmd/kraft/run/runner.go +++ b/cmd/kraft/run/runner.go @@ -32,29 +32,37 @@ type runner interface { // runners is the list of built-in runners which are checked sequentially for // capability. The first to test positive via Runnable is used with the // controller. -func runners() []runner { +func runners() ([]runner, error) { r := []runner{ &runnerLinuxu{}, &runnerKernel{}, &runnerProject{}, } - for _, pm := range packmanager.PackageManagers() { + umbrella, err := packmanager.PackageManagers() + if err != nil { + return nil, err + } + + for _, pm := range umbrella { r = append(r, &runnerPackage{ pm: pm, }) } - return r + return r, nil } // runnersByName is a utility method that returns a map of the available runners // such that their alias name can be quickly looked up. -func runnersByName() map[string]runner { - runners := runners() +func runnersByName() (map[string]runner, error) { + runners, err := runners() + if err != nil { + return nil, err + } ret := make(map[string]runner, len(runners)) for _, runner := range runners { ret[runner.String()] = runner } - return ret + return ret, nil } diff --git a/cmd/kraft/set/set.go b/cmd/kraft/set/set.go index 27f01220e..18d4a633a 100644 --- a/cmd/kraft/set/set.go +++ b/cmd/kraft/set/set.go @@ -75,13 +75,12 @@ func New() *cobra.Command { } func (*Set) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/cmd/kraft/unset/unset.go b/cmd/kraft/unset/unset.go index e1ba03091..effc250c6 100644 --- a/cmd/kraft/unset/unset.go +++ b/cmd/kraft/unset/unset.go @@ -73,13 +73,12 @@ func New() *cobra.Command { } func (*Unset) Pre(cmd *cobra.Command, _ []string) error { - ctx := cmd.Context() - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err := packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) return nil } diff --git a/internal/bootstrap/init.go b/internal/bootstrap/init.go new file mode 100644 index 000000000..56335b3af --- /dev/null +++ b/internal/bootstrap/init.go @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2022, Unikraft GmbH and The KraftKit Authors. +// Licensed under the BSD-3-Clause License (the "License"). +// You may not use this file except in compliance with the License. + +// Package bootstrap lets us keep kraftkit initialization logic in one place. +package bootstrap + +import ( + "context" + + "kraftkit.sh/api" + "kraftkit.sh/machine/qemu" + "kraftkit.sh/manifest" + "kraftkit.sh/oci" + "kraftkit.sh/packmanager" + "kraftkit.sh/unikraft/elfloader" +) + +// InitKraftkit performs a set of kraftkit setup steps. +// It allows us to move away from in-package init() magic. +// It also allows us to propagate initialization errors easily. +func InitKraftkit(ctx context.Context) error { + registerAdditionalFlags() + + if err := registerSchemes(); err != nil { + return err + } + + return registerPackageManagers(ctx) +} + +func registerAdditionalFlags() { + elfloader.RegisterFlags() + manifest.RegisterFlags() + qemu.RegisterFlags() +} + +func registerSchemes() error { + return api.RegisterSchemes() +} + +func registerPackageManagers(ctx context.Context) error { + managerConstructors := []func(u *packmanager.UmbrellaManager) error{ + oci.RegisterPackageManager(), + manifest.RegisterPackageManager(), + } + + return packmanager.InitUmbrellaManager(ctx, managerConstructors) +} diff --git a/machine/qemu/init.go b/machine/qemu/init.go index 58ae04c33..06bbdbc53 100644 --- a/machine/qemu/init.go +++ b/machine/qemu/init.go @@ -474,7 +474,9 @@ func init() { // CLI configuration gob.Register(QemuConfig{}) +} +func RegisterFlags() { // Register additional command-line arguments cmdfactory.RegisterFlag( "kraft run", diff --git a/manifest/init.go b/manifest/init.go index 74f0b05ac..fa95c5205 100644 --- a/manifest/init.go +++ b/manifest/init.go @@ -14,11 +14,13 @@ import ( // and is dynamically injected as a CLI option. var useGit = false -// FIXME(antoineco): avoid init, initialize things where needed -func init() { - // Register a new pack.Package type - _ = packmanager.RegisterPackageManager(ManifestFormat, NewManifestManager) +func RegisterPackageManager() func(u *packmanager.UmbrellaManager) error { + return func(u *packmanager.UmbrellaManager) error { + return u.RegisterPackageManager(ManifestFormat, NewManifestManager) + } +} +func RegisterFlags() { // Register additional command-line flags cmdfactory.RegisterFlag( "kraft pkg pull", diff --git a/oci/init.go b/oci/init.go index 418dbee86..e844d7903 100644 --- a/oci/init.go +++ b/oci/init.go @@ -8,14 +8,14 @@ import ( "kraftkit.sh/packmanager" ) -// FIXME(antoineco): avoid init, initialize things where needed -func init() { - // Register a new pkg.Package type - _ = packmanager.RegisterPackageManager( - OCIFormat, - NewOCIManager, - WithDefaultAuth(), - WithDefaultRegistries(), - WithDetectHandler(), - ) +func RegisterPackageManager() func(u *packmanager.UmbrellaManager) error { + return func(u *packmanager.UmbrellaManager) error { + return u.RegisterPackageManager( + OCIFormat, + NewOCIManager, + WithDefaultAuth(), + WithDefaultRegistries(), + WithDetectHandler(), + ) + } } diff --git a/packmanager/context.go b/packmanager/context.go index 0b1f64c62..ba51ca2f0 100644 --- a/packmanager/context.go +++ b/packmanager/context.go @@ -16,7 +16,7 @@ var ( G = FromContext // PM is the system-access umbrella package manager. - PM = umbrella{} + PM = UmbrellaManager{} ) // contextKey is used to retrieve the package manager from the context. diff --git a/packmanager/umbrella.go b/packmanager/umbrella.go index bfc979663..3fd1c0a8c 100644 --- a/packmanager/umbrella.go +++ b/packmanager/umbrella.go @@ -6,6 +6,7 @@ package packmanager import ( "context" + "errors" "fmt" "github.com/sirupsen/logrus" @@ -15,66 +16,36 @@ import ( "kraftkit.sh/unikraft/component" ) -var ( - packageManagers = make(map[pack.PackageFormat]PackageManager) - packageManagerOpts = make(map[pack.PackageFormat][]any) - packageManagerConstructors = make(map[pack.PackageFormat]NewManagerConstructor) -) - -const UmbrellaFormat pack.PackageFormat = "umbrella" +// UmbrellaInstance is a singleton of our umbrella package manager. +var UmbrellaInstance *UmbrellaManager -func PackageManagers() map[pack.PackageFormat]PackageManager { - return packageManagers +// UmbrellaManager is an ad-hoc package manager capable of cross managing any +// registered package managers. +type UmbrellaManager struct { + packageManagers map[pack.PackageFormat]PackageManager + packageManagerOpts map[pack.PackageFormat][]any + packageManagerConstructors map[pack.PackageFormat]NewManagerConstructor } -func RegisterPackageManager(ctxk pack.PackageFormat, constructor NewManagerConstructor, opts ...any) error { - if _, ok := packageManagerConstructors[ctxk]; ok { - return fmt.Errorf("package manager already registered: %s", ctxk) +// InitUmbrellaManager creates the instance of the umbrella manager singleton. +// It allows us to do dependency injection for package manager constructors. +func InitUmbrellaManager(ctx context.Context, constructors []func(*UmbrellaManager) error) error { + if UmbrellaInstance != nil { + return errors.New("tried to reinitialize the umbrella manager but it already exists") } - packageManagerConstructors[ctxk] = constructor - packageManagerOpts[ctxk] = opts - - return nil -} - -// umbrella is an ad-hoc package manager capable of cross managing any -// registered package manager. -type umbrella struct{} - -// NewUmbrellaManager returns a `PackageManager` which can be used to manipulate -// multiple `PackageManager`s. The purpose is to be able to package, unpackage, -// search and generally manipulate packages of multiple types simultaneously. -func NewUmbrellaManager(ctx context.Context) (PackageManager, error) { - for format, constructor := range packageManagerConstructors { - log.G(ctx).WithField("format", format).Trace("initializing package manager") - - var opts []any - - if pmopts, ok := packageManagerOpts[format]; ok { - opts = pmopts - } - - manager, err := constructor(ctx, opts...) - if err != nil { - log.G(ctx). - WithField("format", format). - Debugf("could not initialize package manager: %v", err) - continue - } - - if format, ok := packageManagers[manager.Format()]; ok { - return nil, fmt.Errorf("package manager already registered: %s", format) - } - - packageManagers[manager.Format()] = manager + umbrellaInstance, err := NewUmbrellaManager(ctx, constructors) + if err != nil { + return err } - return umbrella{}, nil + UmbrellaInstance = umbrellaInstance + + return nil } -func (u umbrella) From(sub pack.PackageFormat) (PackageManager, error) { - for _, manager := range packageManagers { +func (u UmbrellaManager) From(sub pack.PackageFormat) (PackageManager, error) { + for _, manager := range u.packageManagers { if manager.Format() == sub { return manager, nil } @@ -83,8 +54,8 @@ func (u umbrella) From(sub pack.PackageFormat) (PackageManager, error) { return nil, fmt.Errorf("unknown package manager: %s", sub) } -func (u umbrella) Update(ctx context.Context) error { - for _, manager := range packageManagers { +func (u UmbrellaManager) Update(ctx context.Context) error { + for _, manager := range u.packageManagers { log.G(ctx).WithFields(logrus.Fields{ "format": manager.Format(), }).Tracef("updating") @@ -97,8 +68,8 @@ func (u umbrella) Update(ctx context.Context) error { return nil } -func (u umbrella) SetSources(ctx context.Context, sources ...string) error { - for _, manager := range packageManagers { +func (u UmbrellaManager) SetSources(ctx context.Context, sources ...string) error { + for _, manager := range u.packageManagers { err := manager.SetSources(ctx, sources...) if err != nil { return err @@ -108,8 +79,8 @@ func (u umbrella) SetSources(ctx context.Context, sources ...string) error { return nil } -func (u umbrella) AddSource(ctx context.Context, source string) error { - for _, manager := range packageManagers { +func (u UmbrellaManager) AddSource(ctx context.Context, source string) error { + for _, manager := range u.packageManagers { log.G(ctx).WithFields(logrus.Fields{ "format": manager.Format(), "source": source, @@ -123,8 +94,8 @@ func (u umbrella) AddSource(ctx context.Context, source string) error { return nil } -func (u umbrella) RemoveSource(ctx context.Context, source string) error { - for _, manager := range packageManagers { +func (u UmbrellaManager) RemoveSource(ctx context.Context, source string) error { + for _, manager := range u.packageManagers { log.G(ctx).WithFields(logrus.Fields{ "format": manager.Format(), "source": source, @@ -138,10 +109,10 @@ func (u umbrella) RemoveSource(ctx context.Context, source string) error { return nil } -func (u umbrella) Pack(ctx context.Context, source component.Component, opts ...PackOption) ([]pack.Package, error) { +func (u UmbrellaManager) Pack(ctx context.Context, source component.Component, opts ...PackOption) ([]pack.Package, error) { var ret []pack.Package - for _, manager := range packageManagers { + for _, manager := range u.packageManagers { log.G(ctx).WithFields(logrus.Fields{ "format": manager.Format(), "source": source.Name(), @@ -157,10 +128,10 @@ func (u umbrella) Pack(ctx context.Context, source component.Component, opts ... return ret, nil } -func (u umbrella) Unpack(ctx context.Context, source pack.Package, opts ...UnpackOption) ([]component.Component, error) { +func (u UmbrellaManager) Unpack(ctx context.Context, source pack.Package, opts ...UnpackOption) ([]component.Component, error) { var ret []component.Component - for _, manager := range packageManagers { + for _, manager := range u.packageManagers { log.G(ctx).WithFields(logrus.Fields{ "format": manager.Format(), "source": source.Name(), @@ -176,9 +147,9 @@ func (u umbrella) Unpack(ctx context.Context, source pack.Package, opts ...Unpac return ret, nil } -func (u umbrella) Catalog(ctx context.Context, qopts ...QueryOption) ([]pack.Package, error) { +func (u UmbrellaManager) Catalog(ctx context.Context, qopts ...QueryOption) ([]pack.Package, error) { var packages []pack.Package - for _, manager := range packageManagers { + for _, manager := range u.packageManagers { pack, err := manager.Catalog(ctx, qopts...) if err != nil { log.G(ctx). @@ -193,12 +164,12 @@ func (u umbrella) Catalog(ctx context.Context, qopts ...QueryOption) ([]pack.Pac return packages, nil } -func (u umbrella) IsCompatible(ctx context.Context, source string, qopts ...QueryOption) (PackageManager, bool, error) { +func (u UmbrellaManager) IsCompatible(ctx context.Context, source string, qopts ...QueryOption) (PackageManager, bool, error) { if source == "" { return nil, false, fmt.Errorf("cannot determine compatibility of empty source") } - for _, manager := range packageManagers { + for _, manager := range u.packageManagers { log.G(ctx).WithFields(logrus.Fields{ "format": manager.Format(), "source": source, @@ -217,6 +188,95 @@ func (u umbrella) IsCompatible(ctx context.Context, source string, qopts ...Quer return nil, false, fmt.Errorf("cannot find compatible package manager for source: %s", source) } -func (u umbrella) Format() pack.PackageFormat { +func (u *UmbrellaManager) PackageManagers() map[pack.PackageFormat]PackageManager { + return u.packageManagers +} + +func (u *UmbrellaManager) RegisterPackageManager(ctxk pack.PackageFormat, constructor NewManagerConstructor, opts ...any) error { + if u.packageManagerConstructors == nil { + u.packageManagerConstructors = make(map[pack.PackageFormat]NewManagerConstructor) + } + if u.packageManagerOpts == nil { + u.packageManagerOpts = make(map[pack.PackageFormat][]any) + } + if u.packageManagers == nil { + u.packageManagers = make(map[pack.PackageFormat]PackageManager) + } + + if _, ok := u.packageManagerConstructors[ctxk]; ok { + return fmt.Errorf("package manager already registered: %s", ctxk) + } + + u.packageManagerConstructors[ctxk] = constructor + u.packageManagerOpts[ctxk] = opts + + return nil +} + +func (u UmbrellaManager) Format() pack.PackageFormat { return UmbrellaFormat } + +const UmbrellaFormat pack.PackageFormat = "umbrella" + +// PackageManagers returns all package managers registered +// with the umbrella package manager. +func PackageManagers() (map[pack.PackageFormat]PackageManager, error) { + if UmbrellaInstance == nil { + return nil, errors.New("umbrella manager not initialized") + } + return UmbrellaInstance.PackageManagers(), nil +} + +// WithDefaultUmbrellaManagerInContext returns a context containing the +// default umbrella package manager. +func WithDefaultUmbrellaManagerInContext(ctx context.Context) (context.Context, error) { + if UmbrellaInstance == nil { + return nil, errors.New("umbrella manager not initialized") + } + return WithManagerInContext(ctx, UmbrellaInstance), nil +} + +// WithManagerInContext inserts a package manager into a context. +func WithManagerInContext(ctx context.Context, pm PackageManager) context.Context { + return WithPackageManager(ctx, pm) +} + +// NewUmbrellaManager returns a `PackageManager` which can be used to manipulate +// multiple `PackageManager`s. The purpose is to be able to package, unpackage, +// search and generally manipulate packages of multiple types simultaneously. +// The user can pass a slice of constructors to determine which package managers +// are to be included. +func NewUmbrellaManager(ctx context.Context, constructors []func(*UmbrellaManager) error) (*UmbrellaManager, error) { + u := &UmbrellaManager{} + for _, reg := range constructors { + if err := reg(u); err != nil { + return nil, fmt.Errorf("failed registering a package manager: %w", err) + } + } + for format, constructor := range u.packageManagerConstructors { + log.G(ctx).WithField("format", format).Trace("initializing package manager") + + var opts []any + + if pmopts, ok := u.packageManagerOpts[format]; ok { + opts = pmopts + } + + manager, err := constructor(ctx, opts...) + if err != nil { + log.G(ctx). + WithField("format", format). + Debugf("could not initialize package manager: %v", err) + continue + } + + if format, ok := u.packageManagers[manager.Format()]; ok { + return nil, fmt.Errorf("package manager already registered: %s", format) + } + + u.packageManagers[manager.Format()] = manager + } + + return u, nil +} diff --git a/tools/github-action/main.go b/tools/github-action/main.go index 435506629..56e23542d 100644 --- a/tools/github-action/main.go +++ b/tools/github-action/main.go @@ -14,6 +14,7 @@ import ( "github.com/spf13/cobra" "kraftkit.sh/cmdfactory" "kraftkit.sh/config" + "kraftkit.sh/internal/bootstrap" "kraftkit.sh/log" "kraftkit.sh/packmanager" "kraftkit.sh/unikraft/app" @@ -69,12 +70,12 @@ func (opts *GithubAction) Pre(cmd *cobra.Command, args []string) (err error) { log.G(ctx).SetLevel(logrus.TraceLevel) } - pm, err := packmanager.NewUmbrellaManager(ctx) + ctx, err = packmanager.WithDefaultUmbrellaManagerInContext(cmd.Context()) if err != nil { return err } - cmd.SetContext(packmanager.WithPackageManager(ctx, pm)) + cmd.SetContext(ctx) if len(opts.Workdir) == 0 { opts.Workdir, err = os.Getwd() @@ -231,5 +232,10 @@ func main() { // Set up the logger in the context if it is available ctx = log.WithLogger(ctx, logger) + if err := bootstrap.InitKraftkit(ctx); err != nil { + log.G(ctx).Errorf("could not init kraftkit: %v", err) + os.Exit(1) + } + cmdfactory.Main(ctx, cmd) } diff --git a/unikraft/elfloader/init.go b/unikraft/elfloader/init.go index 177a0d02e..57c6e6b49 100644 --- a/unikraft/elfloader/init.go +++ b/unikraft/elfloader/init.go @@ -8,7 +8,7 @@ import "kraftkit.sh/cmdfactory" var defaultPrebuilt string -func init() { +func RegisterFlags() { // Register additional command-line arguments cmdfactory.RegisterFlag( "kraft run",