Skip to content

Commit

Permalink
Feat: support reading CLI options from a file
Browse files Browse the repository at this point in the history
* Configuration is performed inside a YAML file.
* If the config file doesn't exist, it will be ignored.
* Command-line arguments take priority over options set in the file.
  • Loading branch information
murtaza-u committed Jan 14, 2024
1 parent 6066650 commit 0f4b68a
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 74 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ go.work
/.envrc
/bin
/certs
/result
/result
2 changes: 1 addition & 1 deletion cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ func Run() error {
app.Authors = []*cli.Author{
{Name: "Murtaza Udaipurwala", Email: "murtaza@murtazau.xyz"},
}
app.Commands = []*cli.Command{runCmd, serveCmd}
app.Commands = []*cli.Command{runCmd(), serveCmd()}
return app.Run(os.Args)
}
28 changes: 28 additions & 0 deletions cli/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cli

import (
"fmt"
"os"

"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)

func loadConfigIfExists(flags []cli.Flag) cli.BeforeFunc {
def := altsrc.NewMapInputSource("", make(map[interface{}]interface{}))
return altsrc.InitInputSourceWithContext(
flags,
func(ctx *cli.Context) (altsrc.InputSourceContext, error) {
path := ctx.Path("config")
stat, err := os.Stat(path)
if err != nil {
return def, nil
}
if stat.IsDir() {
return def, fmt.Errorf("provided config %q is a directory",
path)
}
return altsrc.NewYamlSourceFromFile(path)
},
)
}
117 changes: 67 additions & 50 deletions cli/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,96 +6,113 @@ import (
"github.com/murtaza-u/fleet/srv"

"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)

var runCmd = &cli.Command{
Name: "run",
Usage: "run the fleet server",
Args: false,
Flags: []cli.Flag{
&cli.UintFlag{
func runFlags() []cli.Flag {
return []cli.Flag{
altsrc.NewUintFlag(&cli.UintFlag{
Name: "rpc-port",
Usage: "Port for the gRPC server",
Value: 2035,
EnvVars: []string{"FLEET_RPC_PORT"},
},
&cli.UintFlag{
}),
altsrc.NewUintFlag(&cli.UintFlag{
Name: "http-port",
Usage: "Port for the http web server",
Value: 8080,
EnvVars: []string{"FLEET_HTTP_PORT"},
},
&cli.BoolFlag{
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "reflect",
Usage: "Enable gRPC reflection",
EnvVars: []string{"FLEET_REFLECT"},
},
&cli.BoolFlag{
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "tls",
Usage: "Enable server-side gRPC TLS",
EnvVars: []string{"FLEET_TLS"},
},
&cli.PathFlag{
}),
altsrc.NewPathFlag(&cli.PathFlag{
Name: "certificate",
Usage: "path to certificate file",
Usage: "Path to certificate file",
Value: filepath.Join("certs", "srv-cert.pem"),
TakesFile: true,
EnvVars: []string{"FLEET_CERTIFICATE_PATH"},
},
&cli.PathFlag{
}),
altsrc.NewPathFlag(&cli.PathFlag{
Name: "private-key",
Usage: "path to private key file",
Usage: "Path to private key file",
Value: filepath.Join("certs", "srv-key.pem"),
TakesFile: true,
EnvVars: []string{"FLEET_PRIVATE_KEY_FILE"},
},
&cli.Int64Flag{
}),
altsrc.NewInt64Flag(&cli.Int64Flag{
Name: "max-http-request-body-size",
Usage: "Maximum size of the HTTP request body",
Value: 1024 * 1024,
EnvVars: []string{"FLEET_MAX_HTTP_REQUEST_BODY_SIZE"},
},
&cli.IntFlag{
}),
altsrc.NewIntFlag(&cli.IntFlag{
Name: "max-grpc-recv-size",
Usage: "Maximum gRPC receive message size",
Value: 1024 * 1024,
EnvVars: []string{"FLEET_MAX_GRPC_RECV_SIZE"},
},
&cli.IntFlag{
}),
altsrc.NewIntFlag(&cli.IntFlag{
Name: "max-grpc-send-size",
Usage: "Maximum gRPC send message size.",
Value: 1024 * 1024,
EnvVars: []string{"FLEET_MAX_GRPC_SEND_SIZE"},
},
&cli.StringFlag{
}),
altsrc.NewStringFlag(&cli.StringFlag{
Name: "serving-url-format",
Usage: "Serving url format",
Value: "http://%s.localhost:8080",
EnvVars: []string{"FLEET_SERVING_URL_FORMAT"},
}),
&cli.PathFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Path to config file",
Value: filepath.Join(".", "config.yaml"),
EnvVars: []string{"FLEET_CONFIG_FILE"},
},
},
Action: func(ctx *cli.Context) error {
opts := []srv.OptFunc{
srv.WithRPCPort(uint16(ctx.Uint("rpc-port"))),
srv.WithHTTPPort(uint16(ctx.Uint("http-port"))),
srv.WithPathToCertificate(ctx.Path("certificate")),
srv.WithPathToPrivateKey(ctx.Path("private-key")),
srv.WithMaxRequestBodySize(ctx.Int64("max-http-request-body-size")),
srv.WithMaxMsgRecvSize(ctx.Int("max-grpc-recv-size")),
srv.WithMaxMsgRecvSize(ctx.Int("max-grpc-send-size")),
srv.WithServingUrlFormat(ctx.String("serving-url-format")),
}
if ctx.Bool("tls") {
opts = append(opts, srv.WithTLS())
}
if ctx.Bool("reflect") {
opts = append(opts, srv.WithReflection())
}
}
}

func runCmd() *cli.Command {
flags := runFlags()

srv, err := srv.New(opts...)
if err != nil {
return err
}
return srv.Run()
},
return &cli.Command{
Name: "run",
Usage: "Runs the fleet server",
Args: false,
Flags: flags,
Before: loadConfigIfExists(flags),
Action: func(ctx *cli.Context) error {
opts := []srv.OptFunc{
srv.WithRPCPort(uint16(ctx.Uint("rpc-port"))),
srv.WithHTTPPort(uint16(ctx.Uint("http-port"))),
srv.WithPathToCertificate(ctx.Path("certificate")),
srv.WithPathToPrivateKey(ctx.Path("private-key")),
srv.WithMaxRequestBodySize(ctx.Int64("max-http-request-body-size")),
srv.WithMaxMsgRecvSize(ctx.Int("max-grpc-recv-size")),
srv.WithMaxMsgRecvSize(ctx.Int("max-grpc-send-size")),
srv.WithServingUrlFormat(ctx.String("serving-url-format")),
}
if ctx.Bool("tls") {
opts = append(opts, srv.WithTLS())
}
if ctx.Bool("reflect") {
opts = append(opts, srv.WithReflection())
}

srv, err := srv.New(opts...)
if err != nil {
return err
}
return srv.Run()
},
}
}
67 changes: 46 additions & 21 deletions cli/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cli
import (
"errors"
"fmt"
"log"
"net/http"
"net/http/httputil"
"net/url"
Expand All @@ -13,40 +14,64 @@ import (

"github.com/lucasepe/codename"
"github.com/urfave/cli/v2"
"github.com/urfave/cli/v2/altsrc"
)

var serveCmd = &cli.Command{
Name: "serve",
Usage: "connect to the fleet server and serve incoming requests",
UsageText: "[options] static|proxy",
Subcommands: []*cli.Command{staticCmd, proxyCmd},
Flags: []cli.Flag{
&cli.BoolFlag{
Name: "tls",
Usage: "Use TLS to connect to the Fleet gRPC server",
},
&cli.StringFlag{
func serveFlags() []cli.Flag {
conf, err := os.UserConfigDir()
if err != nil {
log.Fatal(err)
}
path := filepath.Join(conf, "fleet", "config.yaml")

return []cli.Flag{
altsrc.NewStringFlag(&cli.StringFlag{
Name: "address",
Aliases: []string{"addr", "rpc-addr", "rpc-address"},
Usage: "address(host:port) of the Fleet gRPC server",
Aliases: []string{"addr"},
Usage: "Address(host:port) of the Fleet gRPC server",
Value: "localhost:2035",
},
&cli.PathFlag{
}),
altsrc.NewBoolFlag(&cli.BoolFlag{
Name: "tls",
Usage: "Use TLS to connect to the Fleet gRPC server",
}),
altsrc.NewPathFlag(&cli.PathFlag{
Name: "ca-cert",
Usage: "Path to the CA's certificate file",
Value: filepath.Join("certs", "ca-cert.pem"),
TakesFile: true,
},
}),
&cli.StringFlag{
Name: "propose-subdomain",
Aliases: []string{"subdomain", "propose", "p"},
Usage: "propose a subdomain to register yourself for the current session",
Aliases: []string{"p"},
Usage: "Propose a subdomain to register yourself for the current session",
},
},
&cli.PathFlag{
Name: "config",
Aliases: []string{"c"},
Usage: "Path to config file",
Value: path,
TakesFile: true,
},
}
}

func serveCmd() *cli.Command {
flags := serveFlags()

return &cli.Command{
Name: "serve",
Usage: "Connect to the fleet server and serve incoming requests",
UsageText: "[options] static|proxy",
Subcommands: []*cli.Command{staticCmd, proxyCmd},
Flags: flags,
Before: loadConfigIfExists(flags),
}
}

var staticCmd = &cli.Command{
Name: "static",
Usage: "serves files from the provided directory",
Usage: "Serves files from the provided directory",
UsageText: "[path]",
ArgsUsage: "[path]",
Args: true,
Expand Down Expand Up @@ -80,7 +105,7 @@ var staticCmd = &cli.Command{

var proxyCmd = &cli.Command{
Name: "proxy",
Usage: "proxies incoming requests to the provided URL",
Usage: "Proxies incoming requests to the provided URL",
UsageText: "URL",
ArgsUsage: "URL",
Args: true,
Expand Down
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
pname = "fleet";
version = version;
src = ./.;
vendorHash = "sha256-N29GLrnkLQAZYuU5OzmV2QOAdwv4FWeLbzaJVKP+/J0=";
vendorHash = "sha256-TM9ZfQ/YfSYdLbdhDgJUMHbhFfm+iOc70zVRKclAjf0=";
CGO_ENABLED = 0;
subPackages = [ "cmd/fleet" ];
nativeBuildInputs = [ pkgs.installShellFiles ];
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ require (
)

require (
github.com/BurntSushi/toml v1.3.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
Expand Down

0 comments on commit 0f4b68a

Please sign in to comment.