-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #40 from ethpandaops/feat/cmd-logs
feat(logs): adds new logs cmd
- Loading branch information
Showing
13 changed files
with
450 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package logs | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/ethpandaops/contributoor-installer/cmd/cli/options" | ||
"github.com/ethpandaops/contributoor-installer/internal/sidecar" | ||
"github.com/ethpandaops/contributoor-installer/internal/tui" | ||
"github.com/ethpandaops/contributoor/pkg/config/v1" | ||
"github.com/sirupsen/logrus" | ||
"github.com/urfave/cli" | ||
) | ||
|
||
func RegisterCommands(app *cli.App, opts *options.CommandOpts) { | ||
app.Commands = append(app.Commands, cli.Command{ | ||
Name: "logs", | ||
Usage: "View Contributoor logs", | ||
UsageText: "contributoor logs [options]", | ||
Flags: []cli.Flag{ | ||
cli.IntFlag{ | ||
Name: "tail", | ||
Usage: "Number of lines to show from the end of logs", | ||
Value: 100, | ||
}, | ||
cli.BoolFlag{ | ||
Name: "follow, f", | ||
Usage: "Follow log output", | ||
}, | ||
}, | ||
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("%s%v%s", tui.TerminalColorRed, err, tui.TerminalColorReset) | ||
} | ||
|
||
dockerSidecar, err := sidecar.NewDockerSidecar(log, sidecarCfg, installerCfg) | ||
if err != nil { | ||
return fmt.Errorf("error creating docker sidecar service: %w", err) | ||
} | ||
|
||
systemdSidecar, err := sidecar.NewSystemdSidecar(log, sidecarCfg, installerCfg) | ||
if err != nil { | ||
return fmt.Errorf("error creating systemd sidecar service: %w", err) | ||
} | ||
|
||
binarySidecar, err := sidecar.NewBinarySidecar(log, sidecarCfg, installerCfg) | ||
if err != nil { | ||
return fmt.Errorf("error creating binary sidecar service: %w", err) | ||
} | ||
|
||
return showLogs(c, log, sidecarCfg, dockerSidecar, systemdSidecar, binarySidecar) | ||
}, | ||
}) | ||
} | ||
|
||
func showLogs( | ||
c *cli.Context, | ||
log *logrus.Logger, | ||
sidecarCfg sidecar.ConfigManager, | ||
docker sidecar.DockerSidecar, | ||
systemd sidecar.SystemdSidecar, | ||
binary sidecar.BinarySidecar, | ||
) error { | ||
var ( | ||
runner sidecar.SidecarRunner | ||
cfg = sidecarCfg.Get() | ||
) | ||
|
||
// Get the appropriate runner based on run method. | ||
switch cfg.RunMethod { | ||
case config.RunMethod_RUN_METHOD_DOCKER: | ||
runner = docker | ||
case config.RunMethod_RUN_METHOD_SYSTEMD: | ||
runner = systemd | ||
case config.RunMethod_RUN_METHOD_BINARY: | ||
runner = binary | ||
default: | ||
return fmt.Errorf("invalid sidecar run method: %s", cfg.RunMethod) | ||
} | ||
|
||
// Check if service is running. | ||
running, err := runner.IsRunning() | ||
if err != nil { | ||
log.Errorf("could not check sidecar status: %v", err) | ||
|
||
return err | ||
} | ||
|
||
if !running { | ||
fmt.Printf("%sContributoor is not running%s\n", tui.TerminalColorYellow, tui.TerminalColorReset) | ||
|
||
return nil | ||
} | ||
|
||
return runner.Logs(c.Int("tail"), c.Bool("follow")) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
package logs | ||
|
||
import ( | ||
"errors" | ||
"flag" | ||
"testing" | ||
|
||
"github.com/ethpandaops/contributoor-installer/cmd/cli/options" | ||
sidecarmock "github.com/ethpandaops/contributoor-installer/internal/sidecar/mock" | ||
"github.com/ethpandaops/contributoor/pkg/config/v1" | ||
"github.com/sirupsen/logrus" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/urfave/cli" | ||
"go.uber.org/mock/gomock" | ||
) | ||
|
||
func TestShowLogs(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
tests := []struct { | ||
name string | ||
runMethod config.RunMethod | ||
tailLines int | ||
follow bool | ||
setupMocks func(*sidecarmock.MockConfigManager, *sidecarmock.MockDockerSidecar, *sidecarmock.MockBinarySidecar, *sidecarmock.MockSystemdSidecar) | ||
expectedError string | ||
}{ | ||
{ | ||
name: "docker - shows logs successfully", | ||
runMethod: config.RunMethod_RUN_METHOD_DOCKER, | ||
tailLines: 100, | ||
follow: false, | ||
setupMocks: func(cfg *sidecarmock.MockConfigManager, d *sidecarmock.MockDockerSidecar, b *sidecarmock.MockBinarySidecar, s *sidecarmock.MockSystemdSidecar) { | ||
cfg.EXPECT().Get().Return(&config.Config{ | ||
RunMethod: config.RunMethod_RUN_METHOD_DOCKER, | ||
}).Times(1) | ||
d.EXPECT().IsRunning().Return(true, nil) | ||
d.EXPECT().Logs(100, false).Return(nil) | ||
}, | ||
}, | ||
{ | ||
name: "docker - service not running", | ||
runMethod: config.RunMethod_RUN_METHOD_DOCKER, | ||
tailLines: 100, | ||
follow: false, | ||
setupMocks: func(cfg *sidecarmock.MockConfigManager, d *sidecarmock.MockDockerSidecar, b *sidecarmock.MockBinarySidecar, s *sidecarmock.MockSystemdSidecar) { | ||
cfg.EXPECT().Get().Return(&config.Config{ | ||
RunMethod: config.RunMethod_RUN_METHOD_DOCKER, | ||
}).Times(1) | ||
d.EXPECT().IsRunning().Return(false, nil) | ||
}, | ||
}, | ||
{ | ||
name: "docker - logs fail", | ||
runMethod: config.RunMethod_RUN_METHOD_DOCKER, | ||
tailLines: 100, | ||
follow: false, | ||
setupMocks: func(cfg *sidecarmock.MockConfigManager, d *sidecarmock.MockDockerSidecar, b *sidecarmock.MockBinarySidecar, s *sidecarmock.MockSystemdSidecar) { | ||
cfg.EXPECT().Get().Return(&config.Config{ | ||
RunMethod: config.RunMethod_RUN_METHOD_DOCKER, | ||
}).Times(1) | ||
d.EXPECT().IsRunning().Return(true, nil) | ||
d.EXPECT().Logs(100, false).Return(errors.New("logs failed")) | ||
}, | ||
expectedError: "logs failed", | ||
}, | ||
{ | ||
name: "binary - shows logs successfully", | ||
runMethod: config.RunMethod_RUN_METHOD_BINARY, | ||
tailLines: 50, | ||
follow: true, | ||
setupMocks: func(cfg *sidecarmock.MockConfigManager, d *sidecarmock.MockDockerSidecar, b *sidecarmock.MockBinarySidecar, s *sidecarmock.MockSystemdSidecar) { | ||
cfg.EXPECT().Get().Return(&config.Config{ | ||
RunMethod: config.RunMethod_RUN_METHOD_BINARY, | ||
}).Times(1) | ||
b.EXPECT().IsRunning().Return(true, nil) | ||
b.EXPECT().Logs(50, true).Return(nil) | ||
}, | ||
}, | ||
{ | ||
name: "systemd - shows logs successfully", | ||
runMethod: config.RunMethod_RUN_METHOD_SYSTEMD, | ||
tailLines: 200, | ||
follow: false, | ||
setupMocks: func(cfg *sidecarmock.MockConfigManager, d *sidecarmock.MockDockerSidecar, b *sidecarmock.MockBinarySidecar, s *sidecarmock.MockSystemdSidecar) { | ||
cfg.EXPECT().Get().Return(&config.Config{ | ||
RunMethod: config.RunMethod_RUN_METHOD_SYSTEMD, | ||
}).Times(1) | ||
s.EXPECT().IsRunning().Return(true, nil) | ||
s.EXPECT().Logs(200, false).Return(nil) | ||
}, | ||
}, | ||
{ | ||
name: "invalid sidecar run method", | ||
runMethod: config.RunMethod_RUN_METHOD_UNSPECIFIED, | ||
tailLines: 100, | ||
follow: false, | ||
setupMocks: func(cfg *sidecarmock.MockConfigManager, d *sidecarmock.MockDockerSidecar, b *sidecarmock.MockBinarySidecar, s *sidecarmock.MockSystemdSidecar) { | ||
cfg.EXPECT().Get().Return(&config.Config{ | ||
RunMethod: config.RunMethod_RUN_METHOD_UNSPECIFIED, | ||
}).Times(1) | ||
}, | ||
expectedError: "invalid sidecar run method", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
var ( | ||
mockConfig = sidecarmock.NewMockConfigManager(ctrl) | ||
mockDocker = sidecarmock.NewMockDockerSidecar(ctrl) | ||
mockBinary = sidecarmock.NewMockBinarySidecar(ctrl) | ||
mockSystemd = sidecarmock.NewMockSystemdSidecar(ctrl) | ||
) | ||
|
||
tt.setupMocks(mockConfig, mockDocker, mockBinary, mockSystemd) | ||
|
||
var ( | ||
app = cli.NewApp() | ||
set = flag.NewFlagSet("test", flag.ContinueOnError) | ||
) | ||
|
||
set.Int("tail", tt.tailLines, "") | ||
set.Bool("follow", tt.follow, "") | ||
ctx := cli.NewContext(app, set, nil) | ||
|
||
err := showLogs(ctx, logrus.New(), mockConfig, mockDocker, mockSystemd, mockBinary) | ||
|
||
if tt.expectedError != "" { | ||
assert.ErrorContains(t, err, tt.expectedError) | ||
|
||
return | ||
} | ||
|
||
assert.NoError(t, err) | ||
}) | ||
} | ||
} | ||
|
||
func TestRegisterCommands(t *testing.T) { | ||
ctrl := gomock.NewController(t) | ||
defer ctrl.Finish() | ||
|
||
tests := []struct { | ||
name string | ||
configPath string | ||
expectedError string | ||
}{ | ||
{ | ||
name: "successfully registers command", | ||
configPath: "testdata/valid", | ||
}, | ||
{ | ||
name: "fails when config service fails", | ||
configPath: "/invalid/path/that/doesnt/exist", | ||
expectedError: "directory [/invalid/path/that/doesnt/exist] does not exist", | ||
}, | ||
} | ||
|
||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
// Create dummy CLI app, with the config flag. | ||
app := cli.NewApp() | ||
app.Flags = []cli.Flag{ | ||
cli.StringFlag{ | ||
Name: "config-path", | ||
}, | ||
} | ||
|
||
// Ensure we set the config path flag. | ||
globalSet := flag.NewFlagSet("test", flag.ContinueOnError) | ||
globalSet.String("config-path", "", "") | ||
|
||
err := globalSet.Set("config-path", tt.configPath) | ||
require.NoError(t, err) | ||
|
||
// Create the cmd context. | ||
globalCtx := cli.NewContext(app, globalSet, nil) | ||
app.Metadata = map[string]interface{}{ | ||
"flagContext": globalCtx, | ||
} | ||
|
||
RegisterCommands( | ||
app, | ||
options.NewCommandOpts( | ||
options.WithName("logs"), | ||
options.WithLogger(logrus.New()), | ||
), | ||
) | ||
|
||
if tt.expectedError != "" { | ||
// Ensure the command registration succeeded | ||
assert.NoError(t, err) | ||
|
||
// Assert that the action execution fails as expected. | ||
cmd := app.Commands[0] | ||
ctx := cli.NewContext(app, nil, globalCtx) | ||
|
||
// Assert that the action is the func we expect. | ||
action, ok := cmd.Action.(func(*cli.Context) error) | ||
require.True(t, ok, "expected action to be func(*cli.Context) error") | ||
|
||
// Execute the action and assert the error. | ||
actionErr := action(ctx) | ||
assert.Error(t, actionErr) | ||
assert.ErrorContains(t, actionErr, tt.expectedError) | ||
} else { | ||
// Ensure the command registration succeeded. | ||
assert.NoError(t, err) | ||
assert.Len(t, app.Commands, 1) | ||
|
||
// Ensure the command is registered as expected. | ||
cmd := app.Commands[0] | ||
assert.Equal(t, "logs", cmd.Name) | ||
assert.Equal(t, "View Contributoor logs", cmd.Usage) | ||
assert.Equal(t, "contributoor logs [options]", cmd.UsageText) | ||
assert.NotNil(t, cmd.Action) | ||
|
||
// Verify flags. | ||
assert.Len(t, cmd.Flags, 2) | ||
tailFlag, _ := cmd.Flags[0].(cli.IntFlag) | ||
followFlag, _ := cmd.Flags[1].(cli.BoolFlag) | ||
|
||
assert.Equal(t, "tail", tailFlag.Name) | ||
assert.Equal(t, 100, tailFlag.Value) | ||
assert.Equal(t, "follow, f", followFlag.Name) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.