From 804ce35e3bf962c8142ade3d1ebbd21aeacf2807 Mon Sep 17 00:00:00 2001 From: Jay Pipes Date: Tue, 24 Sep 2024 09:49:01 -0400 Subject: [PATCH] overhaul ghw-snapshot tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the first step in actually overhauling the ghw library's context package and moving to a more standard `context.Context` usage. In order to standardize the context usage, we needed to change the way that the ghw-snapshot functionality worked to manually construct a `pkg/context.Context` and call `pkg/context.Context:Do()` while reading the snapshot tarball. In order to do that, I created a new `ghw-snapshot read` command that accepts a single argument to the snapshot tarball path to read: ``` ➜ ghw git:(overhaul-snapshot) ✗ GHW_DISABLE_WARNINGS=1 go run cmd/ghw-snapshot/main.go read testdata/snapshots/linux-amd64-intel-xeon-L5640.tar.gz block storage (8 disks, 723GB physical storage) cpu (2 physical packages, 12 cores, 24 hardware threads) gpu (0 graphics cards) memory (66GB physical, 63GB usable) net (0 NICs) topology NUMA (2 nodes) chassis type=unknown vendor=unknown version=unknown bios vendor=unknown version=unknown baseboard vendor=unknown version=unknown product=unknown product family=unknown name=unknown vendor=unknown sku=unknown version=unknown PCI (82 devices) ➜ ghw git:(overhaul-snapshot) ✗ GHW_DISABLE_WARNINGS=1 go run cmd/ghw-snapshot/main.go read testdata/snapshots/linux-amd64-amd-ryzen-1600.tar.gz block storage (8 disks, 3TB physical storage) cpu (1 physical package, 6 cores, 12 hardware threads) gpu (1 graphics card) memory (32GB physical, 32GB usable) net (4 NICs) topology NUMA (0 nodes) chassis type=unknown vendor=unknown version=unknown bios vendor=unknown version=unknown baseboard vendor=unknown version=unknown product=unknown product family=unknown name=unknown vendor=unknown sku=unknown version=unknown PCI (43 devices) ``` A followup series of patches will make the aforementioned changes to `pkg/context` and remove the `pkg/context.Context:Do()` method and put that entirely in the `cmd/ghw-snapshot/command/read.go` file which is the only place we actually use it. Signed-off-by: Jay Pipes --- cmd/ghw-snapshot/command/create.go | 85 +++++++++++++++++++++++++++ cmd/ghw-snapshot/command/read.go | 47 +++++++++++++++ cmd/ghw-snapshot/command/root.go | 59 +++++++++++++++++++ cmd/ghw-snapshot/main.go | 93 +----------------------------- 4 files changed, 193 insertions(+), 91 deletions(-) create mode 100644 cmd/ghw-snapshot/command/create.go create mode 100644 cmd/ghw-snapshot/command/read.go create mode 100644 cmd/ghw-snapshot/command/root.go diff --git a/cmd/ghw-snapshot/command/create.go b/cmd/ghw-snapshot/command/create.go new file mode 100644 index 00000000..091d3c90 --- /dev/null +++ b/cmd/ghw-snapshot/command/create.go @@ -0,0 +1,85 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package command + +import ( + "crypto/md5" + "fmt" + "io" + "os" + "runtime" + + "github.com/spf13/cobra" + + "github.com/jaypipes/ghw/pkg/snapshot" +) + +var ( + // output filepath to save snapshot to + outPath string +) + +var createCmd = &cobra.Command{ + Use: "create", + Short: "Creates a new ghw snapshot", + RunE: doCreate, +} + +// doCreate creates a ghw snapshot +func doCreate(cmd *cobra.Command, args []string) error { + scratchDir, err := os.MkdirTemp("", "ghw-snapshot") + if err != nil { + return err + } + defer os.RemoveAll(scratchDir) + + snapshot.SetTraceFunction(trace) + if err = snapshot.CloneTreeInto(scratchDir); err != nil { + return err + } + + if outPath == "" { + outPath, err = defaultOutPath() + if err != nil { + return err + } + trace("using default output filepath %s\n", outPath) + } + + return snapshot.PackFrom(outPath, scratchDir) +} + +func systemFingerprint() (string, error) { + hn, err := os.Hostname() + if err != nil { + return "unknown", err + } + m := md5.New() + _, err = io.WriteString(m, hn) + if err != nil { + return "unknown", err + } + return fmt.Sprintf("%x", m.Sum(nil)), nil +} + +func defaultOutPath() (string, error) { + fp, err := systemFingerprint() + if err != nil { + return "unknown", err + } + return fmt.Sprintf("%s-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH, fp), nil +} + +func init() { + createCmd.PersistentFlags().StringVarP( + &outPath, + "out", "o", + outPath, + "Path to place snapshot. Defaults to file in current directory with name $OS-$ARCH-$HASHSYSTEMNAME.tar.gz", + ) + rootCmd.AddCommand(createCmd) +} diff --git a/cmd/ghw-snapshot/command/read.go b/cmd/ghw-snapshot/command/read.go new file mode 100644 index 00000000..aea3ba6e --- /dev/null +++ b/cmd/ghw-snapshot/command/read.go @@ -0,0 +1,47 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package command + +import ( + "errors" + "fmt" + "os" + + "github.com/spf13/cobra" + + "github.com/jaypipes/ghw" + ghwcontext "github.com/jaypipes/ghw/pkg/context" +) + +var readCmd = &cobra.Command{ + Use: "read", + Short: "Reads a new ghw snapshot", + RunE: doRead, +} + +// doRead reads a ghw snapshot from the input snapshot path argument +func doRead(cmd *cobra.Command, args []string) error { + if len(args) != 1 { + return errors.New("supply a single argument with the filepath to the snapshot you wish to read") + } + inPath := args[0] + if _, err := os.Stat(inPath); err != nil { + return err + } + os.Setenv("GHW_SNAPSHOT_PATH", inPath) + ctx := ghwcontext.New() + + return ctx.Do(func() error { + info, err := ghw.Host() + fmt.Println(info.String()) + return err + }) +} + +func init() { + rootCmd.AddCommand(readCmd) +} diff --git a/cmd/ghw-snapshot/command/root.go b/cmd/ghw-snapshot/command/root.go new file mode 100644 index 00000000..668a9875 --- /dev/null +++ b/cmd/ghw-snapshot/command/root.go @@ -0,0 +1,59 @@ +// +// Use and distribution licensed under the Apache license version 2. +// +// See the COPYING file in the root project directory for full text. +// + +package command + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +var ( + debug bool +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "ghw-snapshot", + Short: "ghw-snapshot - create and read ghw snapshots.", + Long: ` + __ __ __ +.-----.| |--.--.--.--.______.-----.-----.---.-.-----.-----.| |--.-----.| |_ +| _ || | | | |______|__ --| | _ | _ |__ --|| | _ || _| +|___ ||__|__|________| |_____|__|__|___._| __|_____||__|__|_____||____| +|_____| |__| + +Create and read ghw snapshots. + +https://github.com/jaypipes/ghw +`, + RunE: doCreate, +} + +// Execute adds all child commands to the root command and sets flags +// appropriately. This is called by main.main(). It only needs to happen once +// to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func trace(msg string, args ...interface{}) { + if !debug { + return + } + fmt.Printf(msg, args...) +} + +func init() { + rootCmd.PersistentFlags().BoolVar( + &debug, "debug", false, "Enable or disable debug mode", + ) +} diff --git a/cmd/ghw-snapshot/main.go b/cmd/ghw-snapshot/main.go index 754c70ee..ed929342 100644 --- a/cmd/ghw-snapshot/main.go +++ b/cmd/ghw-snapshot/main.go @@ -1,105 +1,16 @@ //go:build linux // +build linux -// // Use and distribution licensed under the Apache license version 2. // // See the COPYING file in the root project directory for full text. -// package main import ( - "crypto/md5" - "fmt" - "io" - "os" - "runtime" - - "github.com/spf13/cobra" - - "github.com/jaypipes/ghw/pkg/snapshot" -) - -var ( - // show debug output - debug = false - // output filepath to save snapshot to - outPath string + "github.com/jaypipes/ghw/cmd/ghw-snapshot/command" ) -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "ghw-snapshot", - Short: "ghw-snapshot - Snapshot filesystem containing system information.", - RunE: execute, -} - -func trace(msg string, args ...interface{}) { - if !debug { - return - } - fmt.Printf(msg, args...) -} - -func systemFingerprint() (string, error) { - hn, err := os.Hostname() - if err != nil { - return "unknown", err - } - m := md5.New() - _, err = io.WriteString(m, hn) - if err != nil { - return "unknown", err - } - return fmt.Sprintf("%x", m.Sum(nil)), nil -} - -func defaultOutPath() (string, error) { - fp, err := systemFingerprint() - if err != nil { - return "unknown", err - } - return fmt.Sprintf("%s-%s-%s.tar.gz", runtime.GOOS, runtime.GOARCH, fp), nil -} - -func execute(cmd *cobra.Command, args []string) error { - scratchDir, err := os.MkdirTemp("", "ghw-snapshot") - if err != nil { - return err - } - defer os.RemoveAll(scratchDir) - - snapshot.SetTraceFunction(trace) - if err = snapshot.CloneTreeInto(scratchDir); err != nil { - return err - } - - if outPath == "" { - outPath, err = defaultOutPath() - if err != nil { - return err - } - trace("using default output filepath %s\n", outPath) - } - - return snapshot.PackFrom(outPath, scratchDir) -} - func main() { - if err := rootCmd.Execute(); err != nil { - trace("execution failed: %v\n", err) - } -} - -func init() { - rootCmd.PersistentFlags().StringVarP( - &outPath, - "out", "o", - outPath, - "Path to place snapshot. Defaults to file in current directory with name $OS-$ARCH-$HASHSYSTEMNAME.tar.gz", - ) - rootCmd.PersistentFlags().BoolVarP( - &debug, "debug", "d", false, "Enable or disable debug mode", - ) + command.Execute() }