Skip to content

Commit

Permalink
Refactor server in autoconfig (#727)
Browse files Browse the repository at this point in the history
This change consolidates instantiating a gRPC server in the `autoconfig`
package.

---------

Co-authored-by: Sebastian (Tiedtke) Huckleberry <[email protected]>
  • Loading branch information
adambabik and sourishkrout authored Jan 21, 2025
1 parent 150494d commit a31494c
Show file tree
Hide file tree
Showing 27 changed files with 369 additions and 355 deletions.
2 changes: 1 addition & 1 deletion internal/cmd/beta/beta_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ All commands use the runme.yaml configuration file.`,
cmd.SetErr(io.Discard)
}

err := autoconfig.InvokeForCommand(func(cfg *config.Config, log *zap.Logger) error {
err := autoconfig.Invoke(func(cfg *config.Config, log *zap.Logger) error {
// Override the filename if provided.
if cFlags.filename != "" {
cfg.Project.Filename = cFlags.filename
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/list_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ List all blocks from the "setup" and "teardown" tags:
runme beta list --tag=setup,teardown
`,
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
proj *project.Project,
filters []project.Filter,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/print_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Print content of commands from the "setup" and "teardown" tags:
runme beta print --tag=setup,teardown
`,
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
proj *project.Project,
filters []project.Filter,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/run_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ Run all blocks from the "setup" and "teardown" tags:
runme beta run --tag=setup,teardown
`,
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
clientFactory autoconfig.ClientFactory,
cmdFactory command.Factory,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/server/server_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ func Cmd() *cobra.Command {
Short: "Commands to manage and call a runme server.",
Hidden: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cfg *config.Config,
) error {
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/server/server_grpcurl_describe_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func serverGRPCurlDescribeCmd() *cobra.Command {
Short: "Describe gRPC services and methods exposed by the server.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cfg *config.Config,
logger *zap.Logger,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/server/server_grpcurl_invoke_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func serverGRPCurlInvokeCmd() *cobra.Command {
Short: "Invoke gRPC command to the server.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cfg *config.Config,
logger *zap.Logger,
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/server/server_grpcurl_list_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func serverGRPCurlListCmd() *cobra.Command {
Short: "List gRPC services exposed by the server.",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cfg *config.Config,
logger *zap.Logger,
Expand Down
23 changes: 3 additions & 20 deletions internal/cmd/beta/server/server_start_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"github.com/spf13/cobra"
"go.uber.org/zap"

"github.com/stateful/runme/v3/internal/command"
"github.com/stateful/runme/v3/internal/config"
"github.com/stateful/runme/v3/internal/config/autoconfig"
"github.com/stateful/runme/v3/internal/server"
Expand All @@ -19,30 +18,16 @@ func serverStartCmd() *cobra.Command {
Use: "start",
Short: "Start a server.",
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cfg *config.Config,
cmdFactory command.Factory,
server *server.Server,
logger *zap.Logger,
) error {
defer logger.Sync()

serverCfg := &server.Config{
Address: cfg.Server.Address,
CertFile: *cfg.Server.Tls.CertFile, // guaranteed by autoconfig
KeyFile: *cfg.Server.Tls.KeyFile, // guaranteed by autoconfig
TLSEnabled: cfg.Server.Tls.Enabled,
}

_ = telemetry.ReportUnlessNoTracking(logger)

logger.Debug("server config", zap.Any("config", serverCfg))

s, err := server.New(serverCfg, cmdFactory, logger)
if err != nil {
return err
}

// When using a unix socket, we want to create a file with server's PID.
if path := pidFileNameFromAddr(cfg.Server.Address); path != "" {
logger.Debug("creating PID file", zap.String("path", path))
Expand All @@ -52,9 +37,7 @@ func serverStartCmd() *cobra.Command {
defer os.Remove(cfg.Server.Address)
}

logger.Debug("starting the server")

return errors.WithStack(s.Serve())
return errors.WithStack(server.Serve())
},
)
},
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/beta/server/server_stop_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ func serverStopCmd() *cobra.Command {
Use: "stop",
Short: "Stop a server.",
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cfg *config.Config,
logger *zap.Logger,
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/beta/session_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func sessionCmd(*commonFlags) *cobra.Command {
All exported variables during the session will be available to the subsequent commands.
`,
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cmdFactory command.Factory,
logger *zap.Logger,
Expand Down Expand Up @@ -133,7 +133,7 @@ func sessionSetupCmd() *cobra.Command {
Use: "setup",
Hidden: true,
RunE: func(cmd *cobra.Command, args []string) error {
return autoconfig.InvokeForCommand(
return autoconfig.Invoke(
func(
cmdFactory command.Factory,
logger *zap.Logger,
Expand Down
4 changes: 2 additions & 2 deletions internal/command/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ func redactConfig(cfg *ProgramConfig) *ProgramConfig {
}

func isShell(cfg *ProgramConfig) bool {
return IsShellProgram(filepath.Base(cfg.ProgramName)) || IsShellLanguage(cfg.LanguageId)
return isShellProgram(filepath.Base(cfg.ProgramName)) || IsShellLanguage(cfg.LanguageId)
}

func IsShellProgram(programName string) bool {
func isShellProgram(programName string) bool {
switch strings.ToLower(programName) {
case "sh", "bash", "zsh", "ksh", "shell":
return true
Expand Down
120 changes: 75 additions & 45 deletions internal/config/autoconfig/autoconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,47 +24,39 @@ import (
"github.com/stateful/runme/v3/internal/command"
"github.com/stateful/runme/v3/internal/config"
"github.com/stateful/runme/v3/internal/dockerexec"
"github.com/stateful/runme/v3/internal/project/projectservice"
"github.com/stateful/runme/v3/internal/runnerv2client"
"github.com/stateful/runme/v3/internal/runnerv2service"
"github.com/stateful/runme/v3/internal/server"
runmetls "github.com/stateful/runme/v3/internal/tls"
parserv1 "github.com/stateful/runme/v3/pkg/api/gen/proto/go/runme/parser/v1"
projectv1 "github.com/stateful/runme/v3/pkg/api/gen/proto/go/runme/project/v1"
runnerv2 "github.com/stateful/runme/v3/pkg/api/gen/proto/go/runme/runner/v2"
"github.com/stateful/runme/v3/pkg/document/editor/editorservice"
"github.com/stateful/runme/v3/pkg/project"
)

var (
container = dig.New()
commandScope = container.Scope("command")
serverScope = container.Scope("server")
)

func DecorateRoot(decorator interface{}, opts ...dig.DecorateOption) error {
return container.Decorate(decorator, opts...)
}
var defaultBuilder = NewBuilder()

// InvokeForCommand is used to invoke the function with the given dependencies.
// The package will automatically figure out how to instantiate them
// using the available configuration.
//
// Use it only for commands because it supports only singletons
// created during the program initialization.
func InvokeForCommand(function interface{}, opts ...dig.InvokeOption) error {
err := commandScope.Invoke(function, opts...)
return dig.RootCause(err)
type Builder struct {
*dig.Container
}

// InvokeForServer is similar to InvokeForCommand, but it does not provide
// all the dependencies, in particular, it does not provide dependencies
// that differ per request.
func InvokeForServer(function interface{}, opts ...dig.InvokeOption) error {
err := serverScope.Invoke(function, opts...)
return dig.RootCause(err)
func NewBuilder() *Builder {
b := Builder{Container: dig.New()}
b.init()
return &b
}

func mustProvide(err error) {
if err != nil {
panic("failed to provide: " + err.Error())
func (b *Builder) init() {
mustProvide := func(err error) {
if err != nil {
panic("failed to provide: " + err.Error())
}
}
}

func init() {
container := b

mustProvide(container.Provide(getClient))
mustProvide(container.Provide(getClientFactory))
mustProvide(container.Provide(getCommandFactory))
Expand All @@ -74,17 +66,31 @@ func init() {
mustProvide(container.Provide(getProject))
mustProvide(container.Provide(getProjectFilters))
mustProvide(container.Provide(getRootConfig))
mustProvide(container.Provide(getUserConfigDir))
mustProvide(container.Provide(getServer))
}

func Decorate(decorator interface{}, opts ...dig.DecorateOption) error {
return defaultBuilder.Decorate(decorator, opts...)
}

// Invoke is used to invoke the function with the given dependencies.
// The package will automatically figure out how to instantiate them
// using the available configuration.
func Invoke(function interface{}, opts ...dig.InvokeOption) error {
err := defaultBuilder.Invoke(function, opts...)
return dig.RootCause(err)
}

func getClient(cfg *config.Config, logger *zap.Logger) (*runnerv2client.Client, error) {
if cfg.Server == nil {
return nil, nil
}

var opts []grpc.DialOption
opts := []grpc.DialOption{
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(cfg.Server.MaxMessageSize)),
}

if cfg.Server.Tls != nil && cfg.Server.Tls.Enabled {
if tls := cfg.Server.Tls; tls != nil && tls.Enabled {
// It's ok to dereference TLS fields because they are checked in [getRootConfig].
tlsConfig, err := runmetls.LoadClientConfig(*cfg.Server.Tls.CertFile, *cfg.Server.Tls.KeyFile)
if err != nil {
Expand All @@ -96,19 +102,22 @@ func getClient(cfg *config.Config, logger *zap.Logger) (*runnerv2client.Client,
opts = append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
}

return runnerv2client.New(
cfg.Server.Address,
logger,
opts...,
)
conn, err := grpc.NewClient(cfg.Server.Address, opts...)
if err != nil {
return nil, errors.WithStack(err)
}
if conn == nil {
return nil, errors.New("client connection is not configured")
}
return runnerv2client.New(conn, logger), nil
}

type ClientFactory func() (*runnerv2client.Client, error)

func getClientFactory(cfg *config.Config, logger *zap.Logger) ClientFactory {
func getClientFactory(cfg *config.Config, logger *zap.Logger) (ClientFactory, error) {
return func() (*runnerv2client.Client, error) {
return getClient(cfg, logger)
}
}, nil
}

func getCommandFactory(docker *dockerexec.Docker, logger *zap.Logger, proj *project.Project) command.Factory {
Expand Down Expand Up @@ -166,7 +175,7 @@ func getLogger(c *config.Config) (*zap.Logger, error) {
}

if c.Log.Verbose {
zapConfig.Level = zap.NewAtomicLevelAt(zap.DebugLevel)
zapConfig.Level = zap.NewAtomicLevelAt(zap.InfoLevel)
zapConfig.Development = true
zapConfig.Encoding = "console"
zapConfig.EncoderConfig = zap.NewDevelopmentEncoderConfig()
Expand Down Expand Up @@ -266,7 +275,7 @@ func getProjectFilters(c *config.Config) ([]project.Filter, error) {
return filters, nil
}

func getRootConfig(cfgLoader *config.Loader, userCfgDir UserConfigDir) (*config.Config, error) {
func getRootConfig(cfgLoader *config.Loader) (*config.Config, error) {
var cfg *config.Config

items, err := cfgLoader.RootConfigs()
Expand All @@ -284,6 +293,11 @@ func getRootConfig(cfgLoader *config.Loader, userCfgDir UserConfigDir) (*config.
if cfg.Server != nil && cfg.Server.Tls != nil && cfg.Server.Tls.Enabled {
tls := cfg.Server.Tls

userCfgDir, err := os.UserConfigDir()
if err != nil {
return nil, errors.WithMessage(err, "failed to get user config directory")
}

if tls.CertFile == nil {
path := filepath.Join(string(userCfgDir), "runme", "tls", "cert.pem")
tls.CertFile = &path
Expand All @@ -297,9 +311,25 @@ func getRootConfig(cfgLoader *config.Loader, userCfgDir UserConfigDir) (*config.
return cfg, nil
}

type UserConfigDir string
func getServer(cfg *config.Config, cmdFactory command.Factory, logger *zap.Logger) (*server.Server, error) {
if cfg.Server == nil {
return nil, nil
}

func getUserConfigDir() (UserConfigDir, error) {
dir, err := os.UserConfigDir()
return UserConfigDir(dir), errors.WithStack(err)
parserService := editorservice.NewParserServiceServer(logger)
projectService := projectservice.NewProjectServiceServer(logger)
runnerService, err := runnerv2service.NewRunnerService(cmdFactory, logger)
if err != nil {
return nil, err
}

return server.New(
cfg,
logger,
func(sr grpc.ServiceRegistrar) {
parserv1.RegisterParserServiceServer(sr, parserService)
projectv1.RegisterProjectServiceServer(sr, projectService)
runnerv2.RegisterRunnerServiceServer(sr, runnerService)
},
)
}
Loading

0 comments on commit a31494c

Please sign in to comment.