1
0
mirror of https://github.com/pocketbase/pocketbase.git synced 2025-01-23 22:12:48 +02:00

[#1267] call app.Bootstrap() before cobra commands execution

This commit is contained in:
Gani Georgiev 2022-12-15 23:20:23 +02:00
parent 8e582acbee
commit c25e67e13d
6 changed files with 189 additions and 20 deletions

View File

@ -14,9 +14,22 @@
- Fixed `~` expressions backslash literal escaping ([#1231](https://github.com/pocketbase/pocketbase/discussions/1231)). - Fixed `~` expressions backslash literal escaping ([#1231](https://github.com/pocketbase/pocketbase/discussions/1231)).
- Changed `core.NewBaseApp(dir, encryptionEnv, isDebug)` to `NewBaseApp(config *BaseAppConfig)` which allows to further configure the app instance. - Refactored the `core.app.Bootstrap()` to be called before starting the cobra commands ([#1267](https://github.com/pocketbase/pocketbase/discussions/1267)).
- ! Changed `pocketbase.NewWithConfig(config Config)` to `pocketbase.NewWithConfig(config *Config)` and added 4 new config settings:
```go
DataMaxOpenConns int // default to core.DefaultDataMaxOpenConns
DataMaxIdleConns int // default to core.DefaultDataMaxIdleConns
LogsMaxOpenConns int // default to core.DefaultLogsMaxOpenConns
LogsMaxIdleConns int // default to core.DefaultLogsMaxIdleConns
```
- Added new helper method `core.App.IsBootstrapped()` to check the current app bootstrap state.
- ! Changed `core.NewBaseApp(dir, encryptionEnv, isDebug)` to `NewBaseApp(config *BaseAppConfig)`.
- ! Removed `rest.UploadedFile` struct (see below `filesystem.File`).
- Removed `rest.UploadedFile` struct (see below `filesystem.File`).
- Added generic file resource struct that allows loading and uploading file content from - Added generic file resource struct that allows loading and uploading file content from
different sources (at the moment multipart/formdata requests and from the local filesystem). different sources (at the moment multipart/formdata requests and from the local filesystem).

View File

@ -78,8 +78,14 @@ type App interface {
// RefreshSettings reinitializes and reloads the stored application settings. // RefreshSettings reinitializes and reloads the stored application settings.
RefreshSettings() error RefreshSettings() error
// IsBootstrapped checks if the application was initialized
// (aka. whether Bootstrap() was called).
IsBootstrapped() bool
// Bootstrap takes care for initializing the application // Bootstrap takes care for initializing the application
// (open db connections, load settings, etc.) // (open db connections, load settings, etc.).
//
// It will call ResetBootstrapState() if the application was already bootstrapped.
Bootstrap() error Bootstrap() error
// ResetBootstrapState takes care for releasing initialized app resources // ResetBootstrapState takes care for releasing initialized app resources

View File

@ -267,8 +267,16 @@ func NewBaseApp(config *BaseAppConfig) *BaseApp {
return app return app
} }
// IsBootstrapped checks if the application was initialized
// (aka. whether Bootstrap() was called).
func (app *BaseApp) IsBootstrapped() bool {
return app.dao != nil && app.logsDao != nil && app.settings != nil
}
// Bootstrap initializes the application // Bootstrap initializes the application
// (aka. create data dir, open db connections, load settings, etc.) // (aka. create data dir, open db connections, load settings, etc.).
//
// It will call ResetBootstrapState() if the application was already bootstrapped.
func (app *BaseApp) Bootstrap() error { func (app *BaseApp) Bootstrap() error {
event := &BootstrapEvent{app} event := &BootstrapEvent{app}

View File

@ -53,11 +53,19 @@ func TestBaseAppBootstrap(t *testing.T) {
}) })
defer app.ResetBootstrapState() defer app.ResetBootstrapState()
if app.IsBootstrapped() {
t.Fatal("Didn't expect the application to be bootstrapped.")
}
// bootstrap // bootstrap
if err := app.Bootstrap(); err != nil { if err := app.Bootstrap(); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !app.IsBootstrapped() {
t.Fatal("Expected the application to be bootstrapped.")
}
if stat, err := os.Stat(testDataDir); err != nil || !stat.IsDir() { if stat, err := os.Stat(testDataDir); err != nil || !stat.IsDir() {
t.Fatal("Expected test data directory to be created.") t.Fatal("Expected test data directory to be created.")
} }

View File

@ -11,6 +11,7 @@ import (
"github.com/pocketbase/pocketbase/cmd" "github.com/pocketbase/pocketbase/cmd"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"github.com/pocketbase/pocketbase/tools/list"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -68,7 +69,7 @@ type Config struct {
func New() *PocketBase { func New() *PocketBase {
_, isUsingGoRun := inspectRuntime() _, isUsingGoRun := inspectRuntime()
return NewWithConfig(Config{ return NewWithConfig(&Config{
DefaultDebug: isUsingGoRun, DefaultDebug: isUsingGoRun,
}) })
} }
@ -80,7 +81,11 @@ func New() *PocketBase {
// Everything will be initialized when [Start()] is executed. // Everything will be initialized when [Start()] is executed.
// If you want to initialize the application before calling [Start()], // If you want to initialize the application before calling [Start()],
// then you'll have to manually call [Bootstrap()]. // then you'll have to manually call [Bootstrap()].
func NewWithConfig(config Config) *PocketBase { func NewWithConfig(config *Config) *PocketBase {
if config == nil {
panic("missing config")
}
// initialize a default data directory based on the executable baseDir // initialize a default data directory based on the executable baseDir
if config.DefaultDataDir == "" { if config.DefaultDataDir == "" {
baseDir, _ := inspectRuntime() baseDir, _ := inspectRuntime()
@ -124,11 +129,6 @@ func NewWithConfig(config Config) *PocketBase {
// hide the default help command (allow only `--help` flag) // hide the default help command (allow only `--help` flag)
pb.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true}) pb.RootCmd.SetHelpCommand(&cobra.Command{Hidden: true})
// hook the bootstrap process
pb.RootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error {
return pb.Bootstrap()
}
return pb return pb
} }
@ -148,6 +148,18 @@ func (pb *PocketBase) Start() error {
// This method differs from pb.Start() by not registering the default // This method differs from pb.Start() by not registering the default
// system commands! // system commands!
func (pb *PocketBase) Execute() error { func (pb *PocketBase) Execute() error {
// Run the bootstrap process if the command exist and it is not
// the default cobra version command to prevent creating
// unnecessary the initialization system files.
//
// https://github.com/pocketbase/pocketbase/issues/404
// https://github.com/pocketbase/pocketbase/discussions/1267
if !pb.skipBootstrap() {
if err := pb.Bootstrap(); err != nil {
return err
}
}
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(1) wg.Add(1)
@ -181,7 +193,7 @@ func (pb *PocketBase) onTerminate() error {
// eagerParseFlags parses the global app flags before calling pb.RootCmd.Execute(). // eagerParseFlags parses the global app flags before calling pb.RootCmd.Execute().
// so we can have all PocketBase flags ready for use on initialization. // so we can have all PocketBase flags ready for use on initialization.
func (pb *PocketBase) eagerParseFlags(config Config) error { func (pb *PocketBase) eagerParseFlags(config *Config) error {
pb.RootCmd.PersistentFlags().StringVar( pb.RootCmd.PersistentFlags().StringVar(
&pb.dataDirFlag, &pb.dataDirFlag,
"dir", "dir",
@ -206,6 +218,46 @@ func (pb *PocketBase) eagerParseFlags(config Config) error {
return pb.RootCmd.ParseFlags(os.Args[1:]) return pb.RootCmd.ParseFlags(os.Args[1:])
} }
// eager check if the app should skip the bootstap process:
// - already bootstrapped
// - is unknown command
// - is the default help command
// - is the default version command
func (pb *PocketBase) skipBootstrap() bool {
flags := []string{
"-h",
"--help",
"-v",
"--version",
}
if pb.IsBootstrapped() {
return true // already bootstrapped
}
cmd, _, err := pb.RootCmd.Find(os.Args[1:])
if err != nil {
return true // unknown command
}
for _, arg := range os.Args {
if !list.ExistInSlice(arg, flags) {
continue
}
// ensure that there is no user defined flag with the same name/shorthand
trimmed := strings.TrimLeft(arg, "-")
if len(trimmed) > 1 && cmd.Flags().Lookup(trimmed) == nil {
return true
}
if len(trimmed) == 1 && cmd.Flags().ShorthandLookup(trimmed) == nil {
return true
}
}
return false
}
// tries to find the base executable directory and how it was run // tries to find the base executable directory and how it was run
func inspectRuntime() (baseDir string, withGoRun bool) { func inspectRuntime() (baseDir string, withGoRun bool) {
if strings.HasPrefix(os.Args[0], os.TempDir()) { if strings.HasPrefix(os.Args[0], os.TempDir()) {

View File

@ -2,20 +2,23 @@ package pocketbase
import ( import (
"os" "os"
"path/filepath"
"testing" "testing"
"github.com/spf13/cobra"
) )
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
// copy os.Args // copy os.Args
originalArgs := []string{} originalArgs := make([]string, len(os.Args))
copy(originalArgs, os.Args) copy(originalArgs, os.Args)
defer func() { defer func() {
// restore os.Args // restore os.Args
copy(os.Args, originalArgs) os.Args = originalArgs
}() }()
// change os.Args // change os.Args
os.Args = os.Args[0:1] os.Args = os.Args[:1]
os.Args = append( os.Args = append(
os.Args, os.Args,
"--dir=test_dir", "--dir=test_dir",
@ -51,7 +54,7 @@ func TestNew(t *testing.T) {
} }
func TestNewWithConfig(t *testing.T) { func TestNewWithConfig(t *testing.T) {
app := NewWithConfig(Config{ app := NewWithConfig(&Config{
DefaultDebug: true, DefaultDebug: true,
DefaultDataDir: "test_dir", DefaultDataDir: "test_dir",
DefaultEncryptionEnv: "test_encryption_env", DefaultEncryptionEnv: "test_encryption_env",
@ -89,15 +92,15 @@ func TestNewWithConfig(t *testing.T) {
func TestNewWithConfigAndFlags(t *testing.T) { func TestNewWithConfigAndFlags(t *testing.T) {
// copy os.Args // copy os.Args
originalArgs := []string{} originalArgs := make([]string, len(os.Args))
copy(originalArgs, os.Args) copy(originalArgs, os.Args)
defer func() { defer func() {
// restore os.Args // restore os.Args
copy(os.Args, originalArgs) os.Args = originalArgs
}() }()
// change os.Args // change os.Args
os.Args = os.Args[0:1] os.Args = os.Args[:1]
os.Args = append( os.Args = append(
os.Args, os.Args,
"--dir=test_dir_flag", "--dir=test_dir_flag",
@ -105,7 +108,7 @@ func TestNewWithConfigAndFlags(t *testing.T) {
"--debug=false", "--debug=false",
) )
app := NewWithConfig(Config{ app := NewWithConfig(&Config{
DefaultDebug: true, DefaultDebug: true,
DefaultDataDir: "test_dir", DefaultDataDir: "test_dir",
DefaultEncryptionEnv: "test_encryption_env", DefaultEncryptionEnv: "test_encryption_env",
@ -140,3 +143,82 @@ func TestNewWithConfigAndFlags(t *testing.T) {
t.Fatal("Expected app.IsDebug() false, got true") t.Fatal("Expected app.IsDebug() false, got true")
} }
} }
func TestSkipBootstrap(t *testing.T) {
// copy os.Args
originalArgs := make([]string, len(os.Args))
copy(originalArgs, os.Args)
defer func() {
// restore os.Args
os.Args = originalArgs
}()
tempDir := filepath.Join(os.TempDir(), "temp_pb_data")
defer os.RemoveAll(tempDir)
// already bootstrapped
app0 := NewWithConfig(&Config{DefaultDataDir: tempDir})
app0.Bootstrap()
if v := app0.skipBootstrap(); !v {
t.Fatal("[bootstrapped] Expected true, got false")
}
// unknown command
os.Args = os.Args[:1]
os.Args = append(os.Args, "demo")
app1 := NewWithConfig(&Config{DefaultDataDir: tempDir})
app1.RootCmd.AddCommand(&cobra.Command{Use: "test"})
if v := app1.skipBootstrap(); !v {
t.Fatal("[unknown] Expected true, got false")
}
// default flags
flagScenarios := []struct {
name string
short string
}{
{"help", "h"},
{"version", "v"},
}
for _, s := range flagScenarios {
// base flag
os.Args = os.Args[:1]
os.Args = append(os.Args, "--"+s.name)
app1 := NewWithConfig(&Config{DefaultDataDir: tempDir})
if v := app1.skipBootstrap(); !v {
t.Fatalf("[--%s] Expected true, got false", s.name)
}
// short flag
os.Args = os.Args[:1]
os.Args = append(os.Args, "-"+s.short)
app2 := NewWithConfig(&Config{DefaultDataDir: tempDir})
if v := app2.skipBootstrap(); !v {
t.Fatalf("[-%s] Expected true, got false", s.short)
}
customCmd := &cobra.Command{Use: "custom"}
customCmd.PersistentFlags().BoolP(s.name, s.short, false, "")
// base flag in custom command
os.Args = os.Args[:1]
os.Args = append(os.Args, "custom")
os.Args = append(os.Args, "--"+s.name)
app3 := NewWithConfig(&Config{DefaultDataDir: tempDir})
app3.RootCmd.AddCommand(customCmd)
if v := app3.skipBootstrap(); v {
t.Fatalf("[--%s custom] Expected false, got true", s.name)
}
// short flag in custom command
os.Args = os.Args[:1]
os.Args = append(os.Args, "custom")
os.Args = append(os.Args, "-"+s.short)
app4 := NewWithConfig(&Config{DefaultDataDir: tempDir})
app4.RootCmd.AddCommand(customCmd)
if v := app4.skipBootstrap(); v {
t.Fatalf("[-%s custom] Expected false, got true", s.short)
}
}
}