diff --git a/pkg/baseboard/baseboard.go b/pkg/baseboard/baseboard.go index ed3a713a..6c5cf5f6 100644 --- a/pkg/baseboard/baseboard.go +++ b/pkg/baseboard/baseboard.go @@ -50,7 +50,14 @@ func (i *Info) String() string { // New returns a pointer to an Info struct containing information about the // host's baseboard func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/bios/bios.go b/pkg/bios/bios.go index 85a7c64b..86b06aed 100644 --- a/pkg/bios/bios.go +++ b/pkg/bios/bios.go @@ -50,7 +50,14 @@ func (i *Info) String() string { // New returns a pointer to a Info struct containing information // about the host's BIOS func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/block/block.go b/pkg/block/block.go index 7a2e4e38..7cf3ac9c 100644 --- a/pkg/block/block.go +++ b/pkg/block/block.go @@ -134,7 +134,14 @@ type Info struct { // New returns a pointer to an Info struct that describes the block storage // resources of the host system. func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/chassis/chassis.go b/pkg/chassis/chassis.go index e11f0447..ec74e373 100644 --- a/pkg/chassis/chassis.go +++ b/pkg/chassis/chassis.go @@ -94,7 +94,14 @@ func (i *Info) String() string { // New returns a pointer to a Info struct containing information // about the host's chassis func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/context/context.go b/pkg/context/context.go index 255d5ebf..9b2e6488 100644 --- a/pkg/context/context.go +++ b/pkg/context/context.go @@ -7,6 +7,8 @@ package context import ( + "fmt" + "github.com/jaypipes/ghw/pkg/option" "github.com/jaypipes/ghw/pkg/snapshot" ) @@ -20,6 +22,8 @@ type Context struct { SnapshotRoot string SnapshotExclusive bool alert option.Alerter + err error + parent *Context } // New returns a Context struct pointer that has had various options set on it @@ -47,10 +51,17 @@ func New(opts ...*option.Option) *Context { ctx.EnableTools = *merged.EnableTools } + // New is not allowed to return error - it would break the established API. + // so the only way out is to actually do the checks here and record the error, + // and return it later, at the earliest possible occasion, in Setup() + if ctx.SnapshotPath != "" && ctx.Chroot != option.DefaultChroot { + // The env/client code supplied a value, but we are will overwrite it when unpacking shapshots! + ctx.err = fmt.Errorf("Conflicting options: chroot %q and snapshot path %q", ctx.Chroot, ctx.SnapshotPath) + } return ctx } -// FromEnv returns an Option that has been populated from the environs or +// FromEnv returns a Context that has been populated from the environs or // default options values func FromEnv() *Context { chrootVal := option.EnvOrDefaultChroot() @@ -67,6 +78,21 @@ func FromEnv() *Context { } } +// FromParent returns a Context which is subordinate to another one. +// This means only the parent Context is in charge to the environment (e.g. setting up the snapshot). +// Subordinate contexts will defer any environment-changing action to their parent. +func FromParent(ctx *Context) *Context { + return &Context{ + Chroot: ctx.Chroot, + EnableTools: ctx.EnableTools, + SnapshotPath: ctx.SnapshotPath, + SnapshotRoot: ctx.SnapshotRoot, + SnapshotExclusive: ctx.SnapshotExclusive, + alert: ctx.alert, + parent: ctx, + } +} + // Do wraps a Setup/Teardown pair around the given function func (ctx *Context) Do(fn func() error) error { err := ctx.Setup() @@ -83,6 +109,13 @@ func (ctx *Context) Do(fn func() error) error { // You should call `Setup` just once. It is safe to call `Setup` if you don't make // use of optional extra features - `Setup` will do nothing. func (ctx *Context) Setup() error { + if ctx.err != nil { + return ctx.err + } + if ctx.parent != nil { + // nothing to do - the parent is in charge + return nil + } if ctx.SnapshotPath == "" { // nothing to do! return nil @@ -111,6 +144,10 @@ func (ctx *Context) Setup() error { // You should always call `Teardown` if you called `Setup` to free any resources // acquired by `Setup`. Check `Do` for more automated management. func (ctx *Context) Teardown() error { + if ctx.parent != nil { + // nothing to do - the parent is in charge + return nil + } if ctx.SnapshotRoot != "" { // if the client code provided the unpack directory, // then it is also in charge of the cleanup. diff --git a/pkg/cpu/cpu.go b/pkg/cpu/cpu.go index 2fa0cd2d..7272b02b 100644 --- a/pkg/cpu/cpu.go +++ b/pkg/cpu/cpu.go @@ -117,7 +117,14 @@ type Info struct { // New returns a pointer to an Info struct that contains information about the // CPUs on the host system func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/gpu/gpu.go b/pkg/gpu/gpu.go index 65864c7e..d0ff9a0e 100644 --- a/pkg/gpu/gpu.go +++ b/pkg/gpu/gpu.go @@ -56,7 +56,14 @@ type Info struct { // New returns a pointer to an Info struct that contains information about the // graphics cards on the host system func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/memory/memory.go b/pkg/memory/memory.go index 93605d2e..5417fe4f 100644 --- a/pkg/memory/memory.go +++ b/pkg/memory/memory.go @@ -35,7 +35,14 @@ type Info struct { } func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/net/net.go b/pkg/net/net.go index 8994d112..3b02c5f9 100644 --- a/pkg/net/net.go +++ b/pkg/net/net.go @@ -49,7 +49,14 @@ type Info struct { // New returns a pointer to an Info struct that contains information about the // network interface controllers (NICs) on the host system func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/option/option.go b/pkg/option/option.go index 0af8b4cb..c3c55f69 100644 --- a/pkg/option/option.go +++ b/pkg/option/option.go @@ -14,7 +14,10 @@ import ( ) const ( - defaultChroot = "/" + DefaultChroot = "/" +) + +const ( envKeyChroot = "GHW_CHROOT" envKeyDisableWarnings = "GHW_DISABLE_WARNINGS" envKeyDisableTools = "GHW_DISABLE_TOOLS" @@ -57,7 +60,7 @@ func EnvOrDefaultChroot() string { if val, exists := os.LookupEnv(envKeyChroot); exists { return val } - return defaultChroot + return DefaultChroot } // EnvOrDefaultSnapshotPath returns the value of the GHW_SNAPSHOT_PATH environs variable diff --git a/pkg/pci/pci.go b/pkg/pci/pci.go index 50f3b9f3..0568c13f 100644 --- a/pkg/pci/pci.go +++ b/pkg/pci/pci.go @@ -151,21 +151,32 @@ func (i *Info) String() string { // New returns a pointer to an Info struct that contains information about the // PCI devices on the host system func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { // by default we don't report NUMA information; // we will only if are sure we are running on NUMA architecture - arch := topology.ARCHITECTURE_SMP - topo, err := topology.NewWithContext(ctx) - if err == nil { - arch = topo.Architecture - } else { - ctx.Warn("error detecting system topology: %v", err) - } info := &Info{ - arch: arch, + arch: topology.ARCHITECTURE_SMP, ctx: ctx, } - if err := ctx.Do(info.load); err != nil { + // we do this trick because we need to make sure ctx.Setup() gets + // a chance to run before any subordinate package is created reusing + // our context. + if err := ctx.Do(func() error { + topo, err := topology.NewWithContext(ctx) + if err == nil { + info.arch = topo.Architecture + } else { + ctx.Warn("error detecting system topology: %v", err) + } + return info.load() + }); err != nil { return nil, err } return info, nil diff --git a/pkg/product/product.go b/pkg/product/product.go index 6a2e1ee0..3c106059 100644 --- a/pkg/product/product.go +++ b/pkg/product/product.go @@ -73,7 +73,14 @@ func (i *Info) String() string { // New returns a pointer to a Info struct containing information // about the host's product func New(opts ...*option.Option) (*Info, error) { - ctx := context.New(opts...) + return newWithContext(context.New(opts...)) +} + +func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err diff --git a/pkg/topology/topology.go b/pkg/topology/topology.go index a846584d..7e846377 100644 --- a/pkg/topology/topology.go +++ b/pkg/topology/topology.go @@ -79,13 +79,17 @@ type Info struct { // New returns a pointer to an Info struct that contains information about the // NUMA topology on the host system func New(opts ...*option.Option) (*Info, error) { - return NewWithContext(context.New(opts...)) + return newWithContext(context.New(opts...)) } // NewWithContext returns a pointer to an Info struct that contains information about // the NUMA topology on the host system. Use this function when you want to consume // the topology package from another package (e.g. pci, gpu) func NewWithContext(ctx *context.Context) (*Info, error) { + return newWithContext(context.FromParent(ctx)) +} + +func newWithContext(ctx *context.Context) (*Info, error) { info := &Info{ctx: ctx} if err := ctx.Do(info.load); err != nil { return nil, err