Skip to content

Commit

Permalink
Merge pull request #11 from ethpandaops/feat/cmd-overrides
Browse files Browse the repository at this point in the history
feat: Add cli override flags
  • Loading branch information
samcm authored Jan 16, 2025
2 parents eb81bbd + e787e42 commit 2ec959a
Show file tree
Hide file tree
Showing 4 changed files with 368 additions and 1 deletion.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,37 @@ go run ./cmd/sentry/main.go --config /path/to/.contributoor/config.yaml --debug
The `config.yaml` would have been generated for you by the installer.
</details>

<details>
<summary>Available CLI Flags</summary>

All configuration options can be overridden via CLI flags:

```bash
--config string # Config file path
--debug # Enable debug mode
--network string # Ethereum network name (mainnet, sepolia, holesky)
--beacon-node-address string # Address of the beacon node API (e.g. http://localhost:5052)
--metrics-address string # Address of the metrics server (e.g. :9091)
--log-level string # Log level (debug, info, warn, error)
--username string # Username for the output server
--password string # Password for the output server
--output-server-address string # Address of the output server (e.g. xatu.primary.production.platform.ethpandaops.io:443)
--output-server-tls string # Enable TLS for the output server (true/false)
--contributoor-directory string # Directory where contributoor stores configuration and data
```

Example with multiple flags:
```bash
go run ./cmd/sentry/main.go \
--config ./config.yaml \
--debug true \
--network sepolia \
--beacon-node-address http://localhost:5052 \
--metrics-address localhost:9091 \
--log-level debug
```
</details>

<details>
<summary>Code Generation</summary>

Expand Down
120 changes: 119 additions & 1 deletion cmd/sentry/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ package main

import (
"context"
"encoding/base64"
"fmt"
"net"
"net/http"
_ "net/http/pprof" //nolint:gosec // pprof only enabled if pprofAddr config is set.
"os"
"os/signal"
"strconv"
"strings"
"syscall"
"time"
Expand All @@ -18,6 +20,7 @@ import (
"github.com/ethpandaops/contributoor/internal/sinks"
"github.com/ethpandaops/contributoor/pkg/config/v1"
"github.com/ethpandaops/contributoor/pkg/ethereum"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
Expand Down Expand Up @@ -60,13 +63,67 @@ func main() {
&cli.StringFlag{
Name: "config",
Usage: "config file path",
Required: true,
Required: false,
},
&cli.BoolFlag{
Name: "debug",
Usage: "debug mode",
Value: false,
},
&cli.StringFlag{
Name: "network",
Usage: "ethereum network name (mainnet, sepolia, holesky)",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "beacon-node-address",
Usage: "address of the beacon node api (e.g. http://localhost:5052)",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "metrics-address",
Usage: "address of the metrics server",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "log-level",
Usage: "log level (debug, info, warn, error)",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "output-server-address",
Usage: "address of the output server",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "username",
Usage: "username for the output server",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "password",
Usage: "password for the output server",
Value: "",
Required: false,
},
// Can't use bool flag here because it doesn't have a "nil" value.
&cli.StringFlag{
Name: "output-server-tls",
Usage: "enable TLS for the output server",
Value: "",
Required: false,
},
&cli.StringFlag{
Name: "contributoor-directory",
Usage: "directory where contributoor stores configuration and data",
Required: false,
},
},
Action: func(c *cli.Context) error {
s, err := newContributoor(c)
Expand Down Expand Up @@ -152,6 +209,10 @@ func newContributoor(c *cli.Context) (*contributoor, error) {
return nil, err
}

if err := applyConfigOverridesFromFlags(cfg, c); err != nil {
return nil, errors.Wrap(err, "failed to apply config overrides from cli flags")
}

return &contributoor{
log: log.WithField("module", "contributoor"),
config: cfg,
Expand Down Expand Up @@ -359,3 +420,60 @@ func (s *contributoor) initBeaconNode() error {

return nil
}

// applyConfigOverridesFromFlags applies CLI flags to the config if they are set.
func applyConfigOverridesFromFlags(cfg *config.Config, c *cli.Context) error {
// Apply CLI flags to config if they are set.
if c.String("network") != "" {
log.Infof("Overriding network to %s", c.String("network"))

cfg.SetNetwork(c.String("network"))
}

if c.String("beacon-node-address") != "" {
log.Infof("Overriding beacon node address")

cfg.SetBeaconNodeAddress(c.String("beacon-node-address"))
}

if c.String("metrics-address") != "" {
log.Infof("Overriding metrics address to %s", c.String("metrics-address"))

cfg.SetMetricsAddress(c.String("metrics-address"))
}

if c.String("log-level") != "" {
log.Infof("Overriding log level to %s", c.String("log-level"))

cfg.SetLogLevel(c.String("log-level"))
}

if c.String("output-server-address") != "" {
log.Infof("Overriding output server address")

cfg.SetOutputServerAddress(c.String("output-server-address"))
}

if c.String("username") != "" || c.String("password") != "" {
log.Infof("Overriding output server credentials")

cfg.SetOutputServerCredentials(
base64.StdEncoding.EncodeToString(
[]byte(fmt.Sprintf("%s:%s", c.String("username"), c.String("password"))),
),
)
}

if c.String("output-server-tls") != "" {
log.Infof("Overriding output server tls to %s", c.String("output-server-tls"))

tls, err := strconv.ParseBool(c.String("output-server-tls"))
if err != nil {
return errors.Wrap(err, "failed to parse output server tls flag")
}

cfg.SetOutputServerTLS(tls)
}

return nil
}
131 changes: 131 additions & 0 deletions cmd/sentry/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import (
"testing"
"time"

"encoding/base64"

"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/v2"
)

func TestStartMetricsServer(t *testing.T) {
Expand Down Expand Up @@ -153,3 +156,131 @@ func waitForServer(t *testing.T, addr string) {

t.Fatalf("Server at %s did not start within deadline", addr)
}

func TestApplyConfigOverridesFromFlags(t *testing.T) {
tests := []struct {
name string
args []string
validate func(*testing.T, *config.Config)
}{
{
name: "network override",
args: []string{"--network", "sepolia"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
assert.Equal(t, config.NetworkName_NETWORK_NAME_SEPOLIA, cfg.NetworkName)
},
},
{
name: "beacon node address override",
args: []string{"--beacon-node-address", "http://localhost:5052"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
assert.Equal(t, "http://localhost:5052", cfg.BeaconNodeAddress)
},
},
{
name: "metrics address override",
args: []string{"--metrics-address", "localhost:9091"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
assert.Equal(t, "localhost:9091", cfg.MetricsAddress)
},
},
{
name: "log level override",
args: []string{"--log-level", "debug"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
assert.Equal(t, "debug", cfg.LogLevel)
},
},
{
name: "output server address override",
args: []string{"--output-server-address", "localhost:8080"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
require.NotNil(t, cfg.OutputServer)
assert.Equal(t, "localhost:8080", cfg.OutputServer.Address)
},
},
{
name: "output server credentials override",
args: []string{"--username", "user", "--password", "pass"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
require.NotNil(t, cfg.OutputServer)
expected := base64.StdEncoding.EncodeToString([]byte("user:pass"))
assert.Equal(t, expected, cfg.OutputServer.Credentials)
},
},
{
name: "output server tls override",
args: []string{"--output-server-tls", "true"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
require.NotNil(t, cfg.OutputServer)
assert.True(t, cfg.OutputServer.Tls)
},
},
{
name: "multiple overrides",
args: []string{
"--network", "sepolia",
"--beacon-node-address", "http://localhost:5052",
"--metrics-address", "localhost:9091",
"--log-level", "debug",
},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
assert.Equal(t, config.NetworkName_NETWORK_NAME_SEPOLIA, cfg.NetworkName)
assert.Equal(t, "http://localhost:5052", cfg.BeaconNodeAddress)
assert.Equal(t, "localhost:9091", cfg.MetricsAddress)
assert.Equal(t, "debug", cfg.LogLevel)
},
},
{
name: "output server credentials override with special chars",
args: []string{"--username", "user", "--password", "pass!@#$%^&*()"},
validate: func(t *testing.T, cfg *config.Config) {
t.Helper()
require.NotNil(t, cfg.OutputServer)
expected := base64.StdEncoding.EncodeToString([]byte("user:pass!@#$%^&*()"))
assert.Equal(t, expected, cfg.OutputServer.Credentials)

// Verify it's valid base64 and decodes back correctly
decoded, err := base64.StdEncoding.DecodeString(cfg.OutputServer.Credentials)
require.NoError(t, err)
assert.Equal(t, "user:pass!@#$%^&*()", string(decoded))
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
app := cli.NewApp()
app.Flags = []cli.Flag{
&cli.StringFlag{Name: "network"},
&cli.StringFlag{Name: "beacon-node-address"},
&cli.StringFlag{Name: "metrics-address"},
&cli.StringFlag{Name: "log-level"},
&cli.StringFlag{Name: "output-server-address"},
&cli.StringFlag{Name: "username"},
&cli.StringFlag{Name: "password"},
&cli.StringFlag{Name: "output-server-tls"},
}

// Create a base config
cfg := &config.Config{}

app.Action = func(c *cli.Context) error {
return applyConfigOverridesFromFlags(cfg, c)
}

err := app.Run(append([]string{"contributoor"}, tt.args...))
require.NoError(t, err)

tt.validate(t, cfg)
})
}
}
Loading

0 comments on commit 2ec959a

Please sign in to comment.