From e425b7e7ec0cca50ed2708e38df4e64d0494452a Mon Sep 17 00:00:00 2001 From: Matty Evans Date: Fri, 20 Dec 2024 08:52:25 +1000 Subject: [PATCH] style: Add import statement for `fmt` in display.go --- cmd/cli/commands/install/display.go | 26 +++-- cmd/cli/commands/start/start.go | 8 +- cmd/cli/commands/status/status.go | 117 ++++++++++++++++++++ cmd/cli/commands/status/status_test.go | 147 +++++++++++++++++++++++++ cmd/cli/commands/stop/stop.go | 8 +- cmd/cli/commands/update/update.go | 8 +- cmd/cli/main.go | 20 +++- cmd/cli/options/command.go | 19 +++- internal/installer/config.go | 5 + 9 files changed, 335 insertions(+), 23 deletions(-) create mode 100644 cmd/cli/commands/status/status.go create mode 100644 cmd/cli/commands/status/status_test.go diff --git a/cmd/cli/commands/install/display.go b/cmd/cli/commands/install/display.go index 67318f7..ffccffe 100644 --- a/cmd/cli/commands/install/display.go +++ b/cmd/cli/commands/install/display.go @@ -1,6 +1,8 @@ package install import ( + "fmt" + "github.com/ethpandaops/contributoor-installer/internal/sidecar" "github.com/ethpandaops/contributoor-installer/internal/tui" "github.com/rivo/tview" @@ -77,7 +79,7 @@ func (d *InstallDisplay) Run() error { "config_path": cfg.ContributoorDirectory, "version": cfg.Version, "run_method": cfg.RunMethod, - }).Info("Running installation wizard") + }).Debug("Running installation wizard") return d.app.Run() } @@ -127,12 +129,22 @@ func (d *InstallDisplay) setPage(page *tui.Page) { // OnComplete is called when the install wizard is complete. func (d *InstallDisplay) OnComplete() error { - d.log.Infof("%sInstallation complete%s", tui.TerminalColorGreen, tui.TerminalColorReset) - d.log.Info("You can now manage contributoor using the following commands:") - d.log.Info(" contributoor start") - d.log.Info(" contributoor stop") - d.log.Info(" contributoor update") - d.log.Info(" contributoor config") + cfg := d.sidecarCfg.Get() + + fmt.Printf("%sContributoor Status%s\n", tui.TerminalColorLightBlue, tui.TerminalColorReset) + fmt.Printf("%-20s: %s\n", "Version", cfg.Version) + fmt.Printf("%-20s: %s\n", "Run Method", cfg.RunMethod) + fmt.Printf("%-20s: %s\n", "Network", cfg.NetworkName) + fmt.Printf("%-20s: %s\n", "Beacon Node", cfg.BeaconNodeAddress) + fmt.Printf("%-20s: %s\n", "Config Path", d.sidecarCfg.GetConfigPath()) + + if cfg.OutputServer != nil { + fmt.Printf("%-20s: %s\n", "Output Server", cfg.OutputServer.Address) + } + + fmt.Printf("\n%sInstallation complete%s\n", tui.TerminalColorGreen, tui.TerminalColorReset) + fmt.Printf("You can now manage contributoor using the following command(s):\n") + fmt.Printf(" contributoor [start|stop|status|update|config]\n") return nil } diff --git a/cmd/cli/commands/start/start.go b/cmd/cli/commands/start/start.go index dc56e48..dfae0c6 100644 --- a/cmd/cli/commands/start/start.go +++ b/cmd/cli/commands/start/start.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/ethpandaops/contributoor-installer/cmd/cli/options" - "github.com/ethpandaops/contributoor-installer/internal/installer" "github.com/ethpandaops/contributoor-installer/internal/sidecar" "github.com/ethpandaops/contributoor-installer/internal/tui" "github.com/sirupsen/logrus" @@ -18,9 +17,10 @@ func RegisterCommands(app *cli.App, opts *options.CommandOpts) { Usage: "Start Contributoor", UsageText: "contributoor start [options]", Action: func(c *cli.Context) error { - log := opts.Logger() - - installerCfg := installer.NewConfig() + var ( + log = opts.Logger() + installerCfg = opts.InstallerConfig() + ) sidecarCfg, err := sidecar.NewConfigService(log, c.GlobalString("config-path")) if err != nil { diff --git a/cmd/cli/commands/status/status.go b/cmd/cli/commands/status/status.go new file mode 100644 index 0000000..1d0d103 --- /dev/null +++ b/cmd/cli/commands/status/status.go @@ -0,0 +1,117 @@ +package status + +import ( + "fmt" + + "github.com/ethpandaops/contributoor-installer/cmd/cli/options" + "github.com/ethpandaops/contributoor-installer/internal/service" + "github.com/ethpandaops/contributoor-installer/internal/sidecar" + "github.com/ethpandaops/contributoor-installer/internal/tui" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +func RegisterCommands(app *cli.App, opts *options.CommandOpts) { + app.Commands = append(app.Commands, cli.Command{ + Name: opts.Name(), + Aliases: opts.Aliases(), + Usage: "Show Contributoor status", + UsageText: "contributoor status [options]", + Action: func(c *cli.Context) error { + var ( + log = opts.Logger() + installerCfg = opts.InstallerConfig() + ) + + sidecarCfg, err := sidecar.NewConfigService(log, c.GlobalString("config-path")) + if err != nil { + return fmt.Errorf("error loading config: %w", err) + } + + dockerSidecar, err := sidecar.NewDockerSidecar(log, sidecarCfg, installerCfg) + if err != nil { + return fmt.Errorf("error creating docker sidecar service: %w", err) + } + + binarySidecar, err := sidecar.NewBinarySidecar(log, sidecarCfg, installerCfg) + if err != nil { + return fmt.Errorf("error creating binary sidecar service: %w", err) + } + + githubService, err := service.NewGitHubService(log, installerCfg) + if err != nil { + return fmt.Errorf("error creating github service: %w", err) + } + + return showStatus(c, log, sidecarCfg, dockerSidecar, binarySidecar, githubService) + }, + }) +} + +func showStatus( + c *cli.Context, + log *logrus.Logger, + config sidecar.ConfigManager, + docker sidecar.DockerSidecar, + binary sidecar.BinarySidecar, + github service.GitHubService, +) error { + var ( + runner sidecar.SidecarRunner + cfg = config.Get() + ) + + // Determine which runner to use. + switch cfg.RunMethod { + case sidecar.RunMethodDocker: + runner = docker + case sidecar.RunMethodBinary: + runner = binary + default: + return fmt.Errorf("invalid sidecar run method: %s", cfg.RunMethod) + } + + // Check if running. + running, err := runner.IsRunning() + if err != nil { + return fmt.Errorf("failed to check status: %w", err) + } + + // Check if there's a newer version available. + var latestVersionLine string + + latestVersion, err := github.GetLatestVersion() + if err == nil && cfg.Version != latestVersion { + latestVersionLine = fmt.Sprintf("%-20s: %s%s%s", "Latest Version", tui.TerminalColorYellow, latestVersion, tui.TerminalColorReset) + } + + // Print status information. + fmt.Printf("%sContributoor Status%s\n", tui.TerminalColorLightBlue, tui.TerminalColorReset) + fmt.Printf("%-20s: %s\n", "Version", cfg.Version) + + if latestVersionLine != "" { + fmt.Printf("%s\n", latestVersionLine) + } + + fmt.Printf("%-20s: %s\n", "Run Method", cfg.RunMethod) + fmt.Printf("%-20s: %s\n", "Network", cfg.NetworkName) + fmt.Printf("%-20s: %s\n", "Beacon Node", cfg.BeaconNodeAddress) + fmt.Printf("%-20s: %s\n", "Config Path", config.GetConfigPath()) + + if cfg.OutputServer != nil { + fmt.Printf("%-20s: %s\n", "Output Server", cfg.OutputServer.Address) + } + + // Print running status with color + statusColor := tui.TerminalColorRed + statusText := "Stopped" + + if running { + statusColor = tui.TerminalColorGreen + statusText = "Running" + } + + fmt.Printf("%-20s: %s%s%s\n", "Status", statusColor, statusText, tui.TerminalColorReset) + + return nil +} diff --git a/cmd/cli/commands/status/status_test.go b/cmd/cli/commands/status/status_test.go new file mode 100644 index 0000000..09330a1 --- /dev/null +++ b/cmd/cli/commands/status/status_test.go @@ -0,0 +1,147 @@ +package status + +import ( + "fmt" + "testing" + + servicemock "github.com/ethpandaops/contributoor-installer/internal/service/mock" + "github.com/ethpandaops/contributoor-installer/internal/sidecar" + "github.com/ethpandaops/contributoor-installer/internal/sidecar/mock" + "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" + "go.uber.org/mock/gomock" +) + +func TestShowStatus(t *testing.T) { + t.Run("shows status for running docker sidecar", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Create mock config with docker setup + mockConfig := mock.NewMockConfigManager(ctrl) + mockConfig.EXPECT().Get().Return(&sidecar.Config{ + Version: "1.0.0", + RunMethod: sidecar.RunMethodDocker, + NetworkName: "mainnet", + BeaconNodeAddress: "http://localhost:5052", + OutputServer: &sidecar.OutputServerConfig{ + Address: "https://output.server", + }, + }).AnyTimes() + mockConfig.EXPECT().GetConfigPath().Return("/path/to/config.yaml") + + // Create mock docker sidecar that's running + mockDocker := mock.NewMockDockerSidecar(ctrl) + mockDocker.EXPECT().IsRunning().Return(true, nil) + + // Create mock binary sidecar (shouldn't be used) + mockBinary := mock.NewMockBinarySidecar(ctrl) + + // Create mock GitHub service + mockGithub := servicemock.NewMockGitHubService(ctrl) + mockGithub.EXPECT().GetLatestVersion().Return("1.0.1", nil) + + err := showStatus( + cli.NewContext(nil, nil, nil), + logrus.New(), + mockConfig, + mockDocker, + mockBinary, + mockGithub, + ) + + assert.NoError(t, err) + }) + + t.Run("shows status for stopped binary sidecar", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Create mock config with binary setup + mockConfig := mock.NewMockConfigManager(ctrl) + mockConfig.EXPECT().Get().Return(&sidecar.Config{ + Version: "1.0.0", + RunMethod: sidecar.RunMethodBinary, + NetworkName: "mainnet", + BeaconNodeAddress: "http://localhost:5052", + }).AnyTimes() + mockConfig.EXPECT().GetConfigPath().Return("/path/to/config.yaml") + + // Create mock docker sidecar (shouldn't be used) + mockDocker := mock.NewMockDockerSidecar(ctrl) + + // Create mock binary sidecar that's stopped + mockBinary := mock.NewMockBinarySidecar(ctrl) + mockBinary.EXPECT().IsRunning().Return(false, nil) + + // Create mock GitHub service with same version (shouldn't show update) + mockGithub := servicemock.NewMockGitHubService(ctrl) + mockGithub.EXPECT().GetLatestVersion().Return("1.0.0", nil) + + err := showStatus( + cli.NewContext(nil, nil, nil), + logrus.New(), + mockConfig, + mockDocker, + mockBinary, + mockGithub, + ) + + assert.NoError(t, err) + }) + + t.Run("handles github service error gracefully", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockConfig := mock.NewMockConfigManager(ctrl) + mockConfig.EXPECT().Get().Return(&sidecar.Config{ + Version: "1.0.0", + RunMethod: sidecar.RunMethodDocker, + NetworkName: "mainnet", + }).AnyTimes() + mockConfig.EXPECT().GetConfigPath().Return("/path/to/config.yaml") + + mockDocker := mock.NewMockDockerSidecar(ctrl) + mockDocker.EXPECT().IsRunning().Return(true, nil) + + // Create mock GitHub service that returns an error + mockGithub := servicemock.NewMockGitHubService(ctrl) + mockGithub.EXPECT().GetLatestVersion().Return("", fmt.Errorf("github error")) + + err := showStatus( + cli.NewContext(nil, nil, nil), + logrus.New(), + mockConfig, + mockDocker, + mock.NewMockBinarySidecar(ctrl), + mockGithub, + ) + + assert.NoError(t, err) // Should still succeed even with GitHub error + }) + + t.Run("handles invalid run method", func(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + // Create mock config with invalid run method + mockConfig := mock.NewMockConfigManager(ctrl) + mockConfig.EXPECT().Get().Return(&sidecar.Config{ + RunMethod: "invalid", + }) + + err := showStatus( + cli.NewContext(nil, nil, nil), + logrus.New(), + mockConfig, + nil, + nil, + nil, + ) + + assert.Error(t, err) + assert.Contains(t, err.Error(), "invalid sidecar run method") + }) +} diff --git a/cmd/cli/commands/stop/stop.go b/cmd/cli/commands/stop/stop.go index a5c49af..0d6ef56 100644 --- a/cmd/cli/commands/stop/stop.go +++ b/cmd/cli/commands/stop/stop.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/ethpandaops/contributoor-installer/cmd/cli/options" - "github.com/ethpandaops/contributoor-installer/internal/installer" "github.com/ethpandaops/contributoor-installer/internal/sidecar" "github.com/ethpandaops/contributoor-installer/internal/tui" "github.com/sirupsen/logrus" @@ -18,9 +17,10 @@ func RegisterCommands(app *cli.App, opts *options.CommandOpts) { Usage: "Stop Contributoor", UsageText: "contributoor stop [options]", Action: func(c *cli.Context) error { - log := opts.Logger() - - installerCfg := installer.NewConfig() + var ( + log = opts.Logger() + installerCfg = opts.InstallerConfig() + ) sidecarCfg, err := sidecar.NewConfigService(log, c.GlobalString("config-path")) if err != nil { diff --git a/cmd/cli/commands/update/update.go b/cmd/cli/commands/update/update.go index b88696b..09e1d9f 100644 --- a/cmd/cli/commands/update/update.go +++ b/cmd/cli/commands/update/update.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/ethpandaops/contributoor-installer/cmd/cli/options" - "github.com/ethpandaops/contributoor-installer/internal/installer" "github.com/ethpandaops/contributoor-installer/internal/service" "github.com/ethpandaops/contributoor-installer/internal/sidecar" "github.com/ethpandaops/contributoor-installer/internal/tui" @@ -26,9 +25,10 @@ func RegisterCommands(app *cli.App, opts *options.CommandOpts) { }, }, Action: func(c *cli.Context) error { - log := opts.Logger() - - installerCfg := installer.NewConfig() + var ( + log = opts.Logger() + installerCfg = opts.InstallerConfig() + ) sidecarCfg, err := sidecar.NewConfigService(log, c.GlobalString("config-path")) if err != nil { diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 48fa22f..14e5be8 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -10,17 +10,26 @@ import ( "github.com/ethpandaops/contributoor-installer/cmd/cli/commands/config" "github.com/ethpandaops/contributoor-installer/cmd/cli/commands/install" "github.com/ethpandaops/contributoor-installer/cmd/cli/commands/start" + "github.com/ethpandaops/contributoor-installer/cmd/cli/commands/status" "github.com/ethpandaops/contributoor-installer/cmd/cli/commands/stop" "github.com/ethpandaops/contributoor-installer/cmd/cli/commands/update" "github.com/ethpandaops/contributoor-installer/cmd/cli/options" + "github.com/ethpandaops/contributoor-installer/internal/installer" "github.com/ethpandaops/contributoor-installer/internal/tui" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) func main() { + installerCfg := installer.NewConfig() + + logLevel, err := logrus.ParseLevel(installerCfg.LogLevel) + if err != nil { + logLevel = logrus.InfoLevel + } + log := logrus.New() - log.SetLevel(logrus.DebugLevel) + log.SetLevel(logLevel) log.SetFormatter(&logrus.TextFormatter{ ForceColors: true, DisableColors: false, @@ -65,16 +74,25 @@ func main() { start.RegisterCommands(app, options.NewCommandOpts( options.WithName("start"), options.WithLogger(log), + options.WithInstallerConfig(installerCfg), )) stop.RegisterCommands(app, options.NewCommandOpts( options.WithName("stop"), options.WithLogger(log), + options.WithInstallerConfig(installerCfg), + )) + + status.RegisterCommands(app, options.NewCommandOpts( + options.WithName("status"), + options.WithLogger(log), + options.WithInstallerConfig(installerCfg), )) update.RegisterCommands(app, options.NewCommandOpts( options.WithName("update"), options.WithLogger(log), + options.WithInstallerConfig(installerCfg), )) config.RegisterCommands(app, options.NewCommandOpts( diff --git a/cmd/cli/options/command.go b/cmd/cli/options/command.go index 418355f..552d786 100644 --- a/cmd/cli/options/command.go +++ b/cmd/cli/options/command.go @@ -1,14 +1,16 @@ package options import ( + "github.com/ethpandaops/contributoor-installer/internal/installer" "github.com/sirupsen/logrus" ) // CommandOpts is the options for a cli command. type CommandOpts struct { - name string - aliases []string - logger *logrus.Logger + name string + aliases []string + logger *logrus.Logger + installerCfg *installer.Config } // NewCommandOpts creates a new CommandOpts with the given options. @@ -45,6 +47,12 @@ func WithLogger(logger *logrus.Logger) CommandOptFunc { } } +func WithInstallerConfig(installerCfg *installer.Config) CommandOptFunc { + return func(o *CommandOpts) { + o.installerCfg = installerCfg + } +} + // Name returns the name of the command. func (o *CommandOpts) Name() string { return o.name @@ -59,3 +67,8 @@ func (o *CommandOpts) Aliases() []string { func (o *CommandOpts) Logger() *logrus.Logger { return o.logger } + +// InstallerConfig returns the installer config for the command. +func (o *CommandOpts) InstallerConfig() *installer.Config { + return o.installerCfg +} diff --git a/internal/installer/config.go b/internal/installer/config.go index 7fe0a4e..7799728 100644 --- a/internal/installer/config.go +++ b/internal/installer/config.go @@ -1,7 +1,11 @@ package installer +import "github.com/sirupsen/logrus" + // Config holds installer-specific configuration that isn't exposed to the sidecar. type Config struct { + // LogLevel is the log level to use for the installer. + LogLevel string // DockerImage is the image name of the sidecar. DockerImage string // DockerTag is the tag of the sidecar. @@ -15,6 +19,7 @@ type Config struct { // NewConfig returns the default installer configuration. func NewConfig() *Config { return &Config{ + LogLevel: logrus.InfoLevel.String(), DockerImage: "ethpandaops/contributoor", GithubOrg: "ethpandaops", GithubRepo: "contributoor",