diff --git a/cmd/sablier/cmd.go b/cmd/sablier/cmd.go index 11a6ec9..7e141bd 100644 --- a/cmd/sablier/cmd.go +++ b/cmd/sablier/cmd.go @@ -1,173 +1,14 @@ package main import ( - "errors" - "fmt" - "log/slog" "os" - "strings" - "time" - "github.com/sablierapp/sablier/cmd/healthcheck" - "github.com/sablierapp/sablier/cmd/version" - "github.com/sablierapp/sablier/pkg/config" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - "github.com/spf13/viper" + "github.com/sablierapp/sablier/pkg/sabliercmd" ) -const ( - // The name of our config file, without the file extension because viper supports many different config file languages. - defaultConfigFilename = "sablier" -) - -var conf = config.NewConfig() -var cfgFile string - func main() { - cmd := NewRootCommand() + cmd := sabliercmd.NewRootCommand() if err := cmd.Execute(); err != nil { os.Exit(1) } } - -func NewRootCommand() *cobra.Command { - rootCmd := &cobra.Command{ - Use: "sablier", - Short: "A webserver to start container on demand", - Long: `Sablier is an API that start containers on demand. -It provides an integrations with multiple reverse proxies and different loading strategies.`, - PersistentPreRunE: func(cmd *cobra.Command, args []string) error { - // You can bind cobra and viper in a few locations, but PersistencePreRunE on the root command works well - return initializeConfig(cmd) - }, - } - - rootCmd.PersistentFlags().StringVar(&cfgFile, "configFile", "", "Config file path. If not defined, looks for sablier.(yml|yaml|toml) in /etc/sablier/ > $XDG_CONFIG_HOME > $HOME/.config/ and current directory") - - startCmd := newStartCommand() - // Provider flags - startCmd.Flags().StringVar(&conf.Provider.Name, "provider.name", "docker", fmt.Sprintf("Provider to use to manage containers %v", config.GetProviders())) - _ = viper.BindPFlag("provider.name", startCmd.Flags().Lookup("provider.name")) - startCmd.Flags().BoolVar(&conf.Provider.AutoStopOnStartup, "provider.auto-stop-on-startup", true, "") - _ = viper.BindPFlag("provider.auto-stop-on-startup", startCmd.Flags().Lookup("provider.auto-stop-on-startup")) - startCmd.Flags().Float32Var(&conf.Provider.Kubernetes.QPS, "provider.kubernetes.qps", 5, "QPS limit for K8S API access client-side throttling") - _ = viper.BindPFlag("provider.kubernetes.qps", startCmd.Flags().Lookup("provider.kubernetes.qps")) - startCmd.Flags().IntVar(&conf.Provider.Kubernetes.Burst, "provider.kubernetes.burst", 10, "Maximum burst for K8S API acees client-side throttling") - _ = viper.BindPFlag("provider.kubernetes.burst", startCmd.Flags().Lookup("provider.kubernetes.burst")) - startCmd.Flags().StringVar(&conf.Provider.Kubernetes.Delimiter, "provider.kubernetes.delimiter", "_", "Delimiter used for namespace/resource type/name resolution. Defaults to \"_\" for backward compatibility. But you should use \"/\" or \".\"") - _ = viper.BindPFlag("provider.kubernetes.delimiter", startCmd.Flags().Lookup("provider.kubernetes.delimiter")) - startCmd.Flags().StringVar(&conf.Provider.Podman.Uri, "provider.podman.uri", "unix:///run/podman/podman.sock", "Uri is the URI to connect to the Podman service.") - _ = viper.BindPFlag("provider.podman.uri", startCmd.Flags().Lookup("provider.podman.uri")) - - // Server flags - startCmd.Flags().IntVar(&conf.Server.Port, "server.port", 10000, "The server port to use") - _ = viper.BindPFlag("server.port", startCmd.Flags().Lookup("server.port")) - startCmd.Flags().StringVar(&conf.Server.BasePath, "server.base-path", "/", "The base path for the API") - _ = viper.BindPFlag("server.base-path", startCmd.Flags().Lookup("server.base-path")) - // Storage flags - startCmd.Flags().StringVar(&conf.Storage.File, "storage.file", "", "File path to save the state") - _ = viper.BindPFlag("storage.file", startCmd.Flags().Lookup("storage.file")) - // Sessions flags - startCmd.Flags().DurationVar(&conf.Sessions.DefaultDuration, "sessions.default-duration", time.Duration(5)*time.Minute, "The default session duration") - _ = viper.BindPFlag("sessions.default-duration", startCmd.Flags().Lookup("sessions.default-duration")) - startCmd.Flags().DurationVar(&conf.Sessions.ExpirationInterval, "sessions.expiration-interval", time.Duration(20)*time.Second, "The expiration checking interval. Higher duration gives less stress on CPU. If you only use sessions of 1h, setting this to 5m is a good trade-off.") - _ = viper.BindPFlag("sessions.expiration-interval", startCmd.Flags().Lookup("sessions.expiration-interval")) - - // logging level - rootCmd.PersistentFlags().StringVar(&conf.Logging.Level, "logging.level", strings.ToLower(slog.LevelInfo.String()), "The logging level. Can be one of [error, warn, info, debug]") - _ = viper.BindPFlag("logging.level", rootCmd.PersistentFlags().Lookup("logging.level")) - - // strategy - startCmd.Flags().StringVar(&conf.Strategy.Dynamic.CustomThemesPath, "strategy.dynamic.custom-themes-path", "", "Custom themes folder, will load all .html files recursively") - _ = viper.BindPFlag("strategy.dynamic.custom-themes-path", startCmd.Flags().Lookup("strategy.dynamic.custom-themes-path")) - startCmd.Flags().StringVar(&conf.Strategy.Dynamic.DefaultTheme, "strategy.dynamic.default-theme", "hacker-terminal", "Default theme used for dynamic strategy") - _ = viper.BindPFlag("strategy.dynamic.default-theme", startCmd.Flags().Lookup("strategy.dynamic.default-theme")) - startCmd.Flags().BoolVar(&conf.Strategy.Dynamic.ShowDetailsByDefault, "strategy.dynamic.show-details-by-default", true, "Show the loading instances details by default") - _ = viper.BindPFlag("strategy.dynamic.show-details-by-default", startCmd.Flags().Lookup("strategy.dynamic.show-details-by-default")) - startCmd.Flags().DurationVar(&conf.Strategy.Dynamic.DefaultRefreshFrequency, "strategy.dynamic.default-refresh-frequency", 5*time.Second, "Default refresh frequency in the HTML page for dynamic strategy") - _ = viper.BindPFlag("strategy.dynamic.default-refresh-frequency", startCmd.Flags().Lookup("strategy.dynamic.default-refresh-frequency")) - startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultTimeout, "strategy.blocking.default-timeout", 1*time.Minute, "Default timeout used for blocking strategy") - _ = viper.BindPFlag("strategy.blocking.default-timeout", startCmd.Flags().Lookup("strategy.blocking.default-timeout")) - startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultRefreshFrequency, "strategy.blocking.default-refresh-frequency", 5*time.Second, "Default refresh frequency at which the instances status are checked for blocking strategy") - _ = viper.BindPFlag("strategy.blocking.default-refresh-frequency", startCmd.Flags().Lookup("strategy.blocking.default-refresh-frequency")) - - rootCmd.AddCommand(startCmd) - rootCmd.AddCommand(version.NewCmd()) - - healthCmd := healthcheck.NewCmd() - healthCmd.Flags().String("url", "http://localhost:10000/health", "Sablier health endpoint") - rootCmd.AddCommand(healthCmd) - - return rootCmd -} - -func initializeConfig(cmd *cobra.Command) error { - v := viper.New() - - // Set the base name of the config file, without the file extension. - v.SetConfigName(defaultConfigFilename) - - v.AddConfigPath("/etc/sablier/") - v.AddConfigPath("$XDG_CONFIG_HOME") - v.AddConfigPath("$HOME/.config/") - v.AddConfigPath(".") - - if cfgFile != "" { - v.SetConfigFile(cfgFile) - } - - // Attempt to read the config file, gracefully ignoring errors - // caused by a config file not being found. Return an error - // if we cannot parse the config file. - if err := v.ReadInConfig(); err != nil { - // It's okay if there isn't a config file - var configFileNotFoundError viper.ConfigFileNotFoundError - if !errors.As(err, &configFileNotFoundError) { - return err - } - } - - // Bind to environment variables - // Works great for simple config names, but needs help for names - // like --favorite-color which we fix in the bindFlags function - v.AutomaticEnv() - - // Bind the current command's flags to viper - bindFlags(cmd, v) - - return nil -} - -// Bind each cobra flag to its associated viper configuration (config file and environment variable) -func bindFlags(cmd *cobra.Command, v *viper.Viper) { - cmd.Flags().VisitAll(func(f *pflag.Flag) { - envVarSuffix := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")) - envVarSuffix = strings.ToUpper(strings.ReplaceAll(envVarSuffix, ".", "_")) - _ = v.BindEnv(f.Name, envVarSuffix) - - // Apply the viper config value to the flag when the flag is not set and viper has a value - if !f.Changed && v.IsSet(f.Name) { - val := v.Get(f.Name) - _ = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) - } - }) -} - -var newStartCommand = func() *cobra.Command { - return &cobra.Command{ - Use: "start", - Short: "Start the Sablier server", - Run: func(cmd *cobra.Command, args []string) { - err := viper.Unmarshal(&conf) - if err != nil { - panic(err) - } - - err = Start(cmd.Context(), conf) - if err != nil { - panic(err) - } - }, - } -} diff --git a/cmd/sablier/cmd_test.go b/pkg/sabliercmd/cmd_test.go similarity index 89% rename from cmd/sablier/cmd_test.go rename to pkg/sabliercmd/cmd_test.go index eadbf23..8f49d81 100644 --- a/cmd/sablier/cmd_test.go +++ b/pkg/sabliercmd/cmd_test.go @@ -1,4 +1,4 @@ -package main +package sabliercmd_test import ( "bufio" @@ -9,8 +9,7 @@ import ( "strings" "testing" - "github.com/sablierapp/sablier/pkg/config" - + "github.com/sablierapp/sablier/pkg/sabliercmd" "github.com/spf13/cobra" "github.com/spf13/viper" "github.com/stretchr/testify/require" @@ -25,11 +24,12 @@ func TestDefault(t *testing.T) { require.NoError(t, err, "error reading test config file") // CHANGE `startCmd` behavior to only print the config, this is for testing purposes only - newStartCommand = mockStartCommand + sabliercmd.SetStartCommand(mockStartCommand) + defer sabliercmd.ResetStartCommand() t.Run("config file", func(t *testing.T) { - conf = config.NewConfig() - cmd := NewRootCommand() + sabliercmd.ResetConfig() + cmd := sabliercmd.NewRootCommand() output := &bytes.Buffer{} cmd.SetOut(output) cmd.SetArgs([]string{ @@ -48,14 +48,15 @@ func TestPrecedence(t *testing.T) { require.NoError(t, err, "error getting the current working directory") // CHANGE `startCmd` behavior to only print the config, this is for testing purposes only - newStartCommand = mockStartCommand + sabliercmd.SetStartCommand(mockStartCommand) + defer sabliercmd.ResetStartCommand() t.Run("config file", func(t *testing.T) { wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_yaml_wanted.json")) require.NoError(t, err, "error reading test config file") - conf = config.NewConfig() - cmd := NewRootCommand() + sabliercmd.ResetConfig() + cmd := sabliercmd.NewRootCommand() output := &bytes.Buffer{} cmd.SetOut(output) cmd.SetArgs([]string{ @@ -76,8 +77,8 @@ func TestPrecedence(t *testing.T) { wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_env_wanted.json")) require.NoError(t, err, "error reading test config file") - conf = config.NewConfig() - cmd := NewRootCommand() + sabliercmd.ResetConfig() + cmd := sabliercmd.NewRootCommand() output := &bytes.Buffer{} cmd.SetOut(output) cmd.SetArgs([]string{ @@ -98,9 +99,9 @@ func TestPrecedence(t *testing.T) { wantConfig, err := os.ReadFile(filepath.Join(testDir, "testdata", "config_cli_wanted.json")) require.NoError(t, err, "error reading test config file") - cmd := NewRootCommand() + cmd := sabliercmd.NewRootCommand() output := &bytes.Buffer{} - conf = config.NewConfig() + sabliercmd.ResetConfig() cmd.SetOut(output) cmd.SetArgs([]string{ "--configFile", filepath.Join(testDir, "testdata", "config.yml"), @@ -174,7 +175,8 @@ func mockStartCommand() *cobra.Command { Use: "start", Short: "InstanceStart the Sablier server", Run: func(cmd *cobra.Command, args []string) { - _ = viper.Unmarshal(&conf) + conf := sabliercmd.GetConfig() + _ = viper.Unmarshal(conf) out := cmd.OutOrStdout() diff --git a/cmd/healthcheck/healthcheck.go b/pkg/sabliercmd/healthcheck.go similarity index 93% rename from cmd/healthcheck/healthcheck.go rename to pkg/sabliercmd/healthcheck.go index 23789e6..3fa6fe7 100644 --- a/cmd/healthcheck/healthcheck.go +++ b/pkg/sabliercmd/healthcheck.go @@ -1,11 +1,12 @@ -package healthcheck +package sabliercmd import ( "fmt" - "github.com/spf13/cobra" "io" "net/http" "os" + + "github.com/spf13/cobra" ) const ( @@ -33,7 +34,7 @@ func Health(url string) (string, bool) { return string(body), healthy } -func NewCmd() *cobra.Command { +func NewHealthCmd() *cobra.Command { return &cobra.Command{ Use: "health", Short: "Calls the health endpoint of a Sablier instance", diff --git a/cmd/sablier/logger.go b/pkg/sabliercmd/logger.go similarity index 97% rename from cmd/sablier/logger.go rename to pkg/sabliercmd/logger.go index b6bba7d..072c17a 100644 --- a/cmd/sablier/logger.go +++ b/pkg/sabliercmd/logger.go @@ -1,12 +1,13 @@ -package main +package sabliercmd import ( - "github.com/lmittmann/tint" - "github.com/sablierapp/sablier/pkg/config" "log/slog" "os" "strings" "time" + + "github.com/lmittmann/tint" + "github.com/sablierapp/sablier/pkg/config" ) func setupLogger(config config.Logging) *slog.Logger { diff --git a/cmd/sablier/provider.go b/pkg/sabliercmd/provider.go similarity index 98% rename from cmd/sablier/provider.go rename to pkg/sabliercmd/provider.go index ca9dbed..6010a84 100644 --- a/cmd/sablier/provider.go +++ b/pkg/sabliercmd/provider.go @@ -1,8 +1,10 @@ -package main +package sabliercmd import ( "context" "fmt" + "log/slog" + "github.com/containers/podman/v5/pkg/bindings" "github.com/docker/docker/client" "github.com/sablierapp/sablier/pkg/config" @@ -13,7 +15,6 @@ import ( "github.com/sablierapp/sablier/pkg/sablier" k8s "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" - "log/slog" ) func setupProvider(ctx context.Context, logger *slog.Logger, config config.Provider) (sablier.Provider, error) { diff --git a/pkg/sabliercmd/root.go b/pkg/sabliercmd/root.go new file mode 100644 index 0000000..acf920d --- /dev/null +++ b/pkg/sabliercmd/root.go @@ -0,0 +1,146 @@ +package sabliercmd + +import ( + "errors" + "fmt" + "log/slog" + "strings" + "time" + + "github.com/sablierapp/sablier/pkg/config" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + // The name of our config file, without the file extension because viper supports many different config file languages. + defaultConfigFilename = "sablier" +) + +var conf = config.NewConfig() +var cfgFile string + +// NewRootCommand creates the root cobra command +func NewRootCommand() *cobra.Command { + rootCmd := &cobra.Command{ + Use: "sablier", + Short: "A webserver to start container on demand", + Long: `Sablier is an API that starts containers on demand. +It provides integrations with multiple reverse proxies and different loading strategies.`, + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + // You can bind cobra and viper in a few locations, but PersistencePreRunE on the root command works well + return initializeConfig(cmd) + }, + } + + rootCmd.PersistentFlags().StringVar(&cfgFile, "configFile", "", "Config file path. If not defined, looks for sablier.(yml|yaml|toml) in /etc/sablier/ > $XDG_CONFIG_HOME > $HOME/.config/ and current directory") + + startCmd := newStartCommand() + // Provider flags + startCmd.Flags().StringVar(&conf.Provider.Name, "provider.name", "docker", fmt.Sprintf("Provider to use to manage containers %v", config.GetProviders())) + _ = viper.BindPFlag("provider.name", startCmd.Flags().Lookup("provider.name")) + startCmd.Flags().BoolVar(&conf.Provider.AutoStopOnStartup, "provider.auto-stop-on-startup", true, "") + _ = viper.BindPFlag("provider.auto-stop-on-startup", startCmd.Flags().Lookup("provider.auto-stop-on-startup")) + startCmd.Flags().Float32Var(&conf.Provider.Kubernetes.QPS, "provider.kubernetes.qps", 5, "QPS limit for K8S API access client-side throttling") + _ = viper.BindPFlag("provider.kubernetes.qps", startCmd.Flags().Lookup("provider.kubernetes.qps")) + startCmd.Flags().IntVar(&conf.Provider.Kubernetes.Burst, "provider.kubernetes.burst", 10, "Maximum burst for K8S API acees client-side throttling") + _ = viper.BindPFlag("provider.kubernetes.burst", startCmd.Flags().Lookup("provider.kubernetes.burst")) + startCmd.Flags().StringVar(&conf.Provider.Kubernetes.Delimiter, "provider.kubernetes.delimiter", "_", "Delimiter used for namespace/resource type/name resolution. Defaults to \"_\" for backward compatibility. But you should use \"/\" or \".\"") + _ = viper.BindPFlag("provider.kubernetes.delimiter", startCmd.Flags().Lookup("provider.kubernetes.delimiter")) + startCmd.Flags().StringVar(&conf.Provider.Podman.Uri, "provider.podman.uri", "unix:///run/podman/podman.sock", "Uri is the URI to connect to the Podman service.") + _ = viper.BindPFlag("provider.podman.uri", startCmd.Flags().Lookup("provider.podman.uri")) + + // Server flags + startCmd.Flags().IntVar(&conf.Server.Port, "server.port", 10000, "The server port to use") + _ = viper.BindPFlag("server.port", startCmd.Flags().Lookup("server.port")) + startCmd.Flags().StringVar(&conf.Server.BasePath, "server.base-path", "/", "The base path for the API") + _ = viper.BindPFlag("server.base-path", startCmd.Flags().Lookup("server.base-path")) + // Storage flags + startCmd.Flags().StringVar(&conf.Storage.File, "storage.file", "", "File path to save the state") + _ = viper.BindPFlag("storage.file", startCmd.Flags().Lookup("storage.file")) + // Sessions flags + startCmd.Flags().DurationVar(&conf.Sessions.DefaultDuration, "sessions.default-duration", time.Duration(5)*time.Minute, "The default session duration") + _ = viper.BindPFlag("sessions.default-duration", startCmd.Flags().Lookup("sessions.default-duration")) + startCmd.Flags().DurationVar(&conf.Sessions.ExpirationInterval, "sessions.expiration-interval", time.Duration(20)*time.Second, "The expiration checking interval. Higher duration gives less stress on CPU. If you only use sessions of 1h, setting this to 5m is a good trade-off.") + _ = viper.BindPFlag("sessions.expiration-interval", startCmd.Flags().Lookup("sessions.expiration-interval")) + + // logging level + rootCmd.PersistentFlags().StringVar(&conf.Logging.Level, "logging.level", strings.ToLower(slog.LevelInfo.String()), "The logging level. Can be one of [error, warn, info, debug]") + _ = viper.BindPFlag("logging.level", rootCmd.PersistentFlags().Lookup("logging.level")) + + // strategy + startCmd.Flags().StringVar(&conf.Strategy.Dynamic.CustomThemesPath, "strategy.dynamic.custom-themes-path", "", "Custom themes folder, will load all .html files recursively") + _ = viper.BindPFlag("strategy.dynamic.custom-themes-path", startCmd.Flags().Lookup("strategy.dynamic.custom-themes-path")) + startCmd.Flags().StringVar(&conf.Strategy.Dynamic.DefaultTheme, "strategy.dynamic.default-theme", "hacker-terminal", "Default theme used for dynamic strategy") + _ = viper.BindPFlag("strategy.dynamic.default-theme", startCmd.Flags().Lookup("strategy.dynamic.default-theme")) + startCmd.Flags().BoolVar(&conf.Strategy.Dynamic.ShowDetailsByDefault, "strategy.dynamic.show-details-by-default", true, "Show the loading instances details by default") + _ = viper.BindPFlag("strategy.dynamic.show-details-by-default", startCmd.Flags().Lookup("strategy.dynamic.show-details-by-default")) + startCmd.Flags().DurationVar(&conf.Strategy.Dynamic.DefaultRefreshFrequency, "strategy.dynamic.default-refresh-frequency", 5*time.Second, "Default refresh frequency in the HTML page for dynamic strategy") + _ = viper.BindPFlag("strategy.dynamic.default-refresh-frequency", startCmd.Flags().Lookup("strategy.dynamic.default-refresh-frequency")) + startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultTimeout, "strategy.blocking.default-timeout", 1*time.Minute, "Default timeout used for blocking strategy") + _ = viper.BindPFlag("strategy.blocking.default-timeout", startCmd.Flags().Lookup("strategy.blocking.default-timeout")) + startCmd.Flags().DurationVar(&conf.Strategy.Blocking.DefaultRefreshFrequency, "strategy.blocking.default-refresh-frequency", 5*time.Second, "Default refresh frequency at which the instances status are checked for blocking strategy") + _ = viper.BindPFlag("strategy.blocking.default-refresh-frequency", startCmd.Flags().Lookup("strategy.blocking.default-refresh-frequency")) + + rootCmd.AddCommand(startCmd) + rootCmd.AddCommand(NewVersionCmd()) + + healthCmd := NewHealthCmd() + healthCmd.Flags().String("url", "http://localhost:10000/health", "Sablier health endpoint") + rootCmd.AddCommand(healthCmd) + + return rootCmd +} + +func initializeConfig(cmd *cobra.Command) error { + v := viper.New() + + // Set the base name of the config file, without the file extension. + v.SetConfigName(defaultConfigFilename) + + v.AddConfigPath("/etc/sablier/") + v.AddConfigPath("$XDG_CONFIG_HOME") + v.AddConfigPath("$HOME/.config/") + v.AddConfigPath(".") + + if cfgFile != "" { + v.SetConfigFile(cfgFile) + } + + // Attempt to read the config file, gracefully ignoring errors + // caused by a config file not being found. Return an error + // if we cannot parse the config file. + if err := v.ReadInConfig(); err != nil { + // It's okay if there isn't a config file + var configFileNotFoundError viper.ConfigFileNotFoundError + if !errors.As(err, &configFileNotFoundError) { + return err + } + } + + // Bind to environment variables + // Works great for simple config names, but needs help for names + // like --favorite-color which we fix in the bindFlags function + v.AutomaticEnv() + + // Bind the current command's flags to viper + bindFlags(cmd, v) + + return nil +} + +// Bind each cobra flag to its associated viper configuration (config file and environment variable) +func bindFlags(cmd *cobra.Command, v *viper.Viper) { + cmd.Flags().VisitAll(func(f *pflag.Flag) { + envVarSuffix := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")) + envVarSuffix = strings.ToUpper(strings.ReplaceAll(envVarSuffix, ".", "_")) + _ = v.BindEnv(f.Name, envVarSuffix) + + // Apply the viper config value to the flag when the flag is not set and viper has a value + if !f.Changed && v.IsSet(f.Name) { + val := v.Get(f.Name) + _ = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) + } + }) +} diff --git a/cmd/sablier/sablier.go b/pkg/sabliercmd/start.go similarity index 84% rename from cmd/sablier/sablier.go rename to pkg/sabliercmd/start.go index 4ac167b..bdc2052 100644 --- a/cmd/sablier/sablier.go +++ b/pkg/sabliercmd/start.go @@ -1,21 +1,42 @@ -package main +package sabliercmd import ( "context" "fmt" - "github.com/sablierapp/sablier/internal/api" - "github.com/sablierapp/sablier/pkg/config" - "github.com/sablierapp/sablier/pkg/sablier" - "github.com/sablierapp/sablier/pkg/store/inmemory" - "github.com/sablierapp/sablier/pkg/version" "log/slog" "os/signal" "syscall" "time" + "github.com/sablierapp/sablier/internal/api" "github.com/sablierapp/sablier/internal/server" + "github.com/sablierapp/sablier/pkg/config" + "github.com/sablierapp/sablier/pkg/sablier" + "github.com/sablierapp/sablier/pkg/store/inmemory" + "github.com/sablierapp/sablier/pkg/version" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) +var newStartCommand = func() *cobra.Command { + return &cobra.Command{ + Use: "start", + Short: "Start the Sablier server", + Run: func(cmd *cobra.Command, args []string) { + err := viper.Unmarshal(&conf) + if err != nil { + panic(err) + } + + err = Start(cmd.Context(), conf) + if err != nil { + panic(err) + } + }, + } +} + +// Start starts the Sablier server func Start(ctx context.Context, conf config.Config) error { // Create context that listens for the interrupt signal from the OS. ctx, stop := signal.NotifyContext(ctx, syscall.SIGINT, syscall.SIGTERM) diff --git a/cmd/sablier/testdata/config.env b/pkg/sabliercmd/testdata/config.env similarity index 100% rename from cmd/sablier/testdata/config.env rename to pkg/sabliercmd/testdata/config.env diff --git a/cmd/sablier/testdata/config.yml b/pkg/sabliercmd/testdata/config.yml similarity index 100% rename from cmd/sablier/testdata/config.yml rename to pkg/sabliercmd/testdata/config.yml diff --git a/cmd/sablier/testdata/config_cli_wanted.json b/pkg/sabliercmd/testdata/config_cli_wanted.json similarity index 100% rename from cmd/sablier/testdata/config_cli_wanted.json rename to pkg/sabliercmd/testdata/config_cli_wanted.json diff --git a/cmd/sablier/testdata/config_default.json b/pkg/sabliercmd/testdata/config_default.json similarity index 100% rename from cmd/sablier/testdata/config_default.json rename to pkg/sabliercmd/testdata/config_default.json diff --git a/cmd/sablier/testdata/config_env_wanted.json b/pkg/sabliercmd/testdata/config_env_wanted.json similarity index 100% rename from cmd/sablier/testdata/config_env_wanted.json rename to pkg/sabliercmd/testdata/config_env_wanted.json diff --git a/cmd/sablier/testdata/config_yaml_wanted.json b/pkg/sabliercmd/testdata/config_yaml_wanted.json similarity index 100% rename from cmd/sablier/testdata/config_yaml_wanted.json rename to pkg/sabliercmd/testdata/config_yaml_wanted.json diff --git a/pkg/sabliercmd/testing.go b/pkg/sabliercmd/testing.go new file mode 100644 index 0000000..78d8f74 --- /dev/null +++ b/pkg/sabliercmd/testing.go @@ -0,0 +1,29 @@ +package sabliercmd + +import ( + "github.com/sablierapp/sablier/pkg/config" + "github.com/spf13/cobra" +) + +// NewStartCommand is a variable that allows mocking the start command for testing +var NewStartCommand = newStartCommand + +// SetStartCommand allows tests to override the start command +func SetStartCommand(cmd func() *cobra.Command) { + newStartCommand = cmd +} + +// ResetStartCommand resets the start command to the default +func ResetStartCommand() { + newStartCommand = NewStartCommand +} + +// GetConfig returns the current configuration (for testing) +func GetConfig() *config.Config { + return &conf +} + +// ResetConfig resets the configuration to a new instance (for testing) +func ResetConfig() { + conf = config.NewConfig() +} diff --git a/cmd/sablier/theme.go b/pkg/sabliercmd/theme.go similarity index 97% rename from cmd/sablier/theme.go rename to pkg/sabliercmd/theme.go index 80ed760..b046eed 100644 --- a/cmd/sablier/theme.go +++ b/pkg/sabliercmd/theme.go @@ -1,11 +1,12 @@ -package main +package sabliercmd import ( "context" - "github.com/sablierapp/sablier/pkg/config" - "github.com/sablierapp/sablier/pkg/theme" "log/slog" "os" + + "github.com/sablierapp/sablier/pkg/config" + "github.com/sablierapp/sablier/pkg/theme" ) func setupTheme(ctx context.Context, conf config.Config, logger *slog.Logger) (*theme.Themes, error) { diff --git a/cmd/version/version.go b/pkg/sabliercmd/version.go similarity index 81% rename from cmd/version/version.go rename to pkg/sabliercmd/version.go index af94bf3..99fd910 100644 --- a/cmd/version/version.go +++ b/pkg/sabliercmd/version.go @@ -1,13 +1,14 @@ -package version +package sabliercmd import ( "fmt" + "github.com/sablierapp/sablier/pkg/version" "github.com/spf13/cobra" ) -func NewCmd() *cobra.Command { +func NewVersionCmd() *cobra.Command { return &cobra.Command{ Use: "version", Short: "Print the version Sablier",