Config: Improve initialization, migrations, and debug logs

This commit is contained in:
Michael Mayer 2022-01-05 18:15:39 +01:00
parent 5462b1e69e
commit d67e325854
13 changed files with 88 additions and 43 deletions

View file

@ -170,6 +170,8 @@ func (c *Config) Propagate() {
// Init creates directories, parses additional config files, opens a database connection and initializes dependencies.
func (c *Config) Init() error {
start := time.Now()
if err := c.CreateDirectories(); err != nil {
return err
}
@ -215,7 +217,13 @@ func (c *Config) Init() error {
c.Propagate()
return c.connectDb()
err := c.connectDb()
if err == nil {
log.Debugf("config: successfully initialized [%s]", time.Since(start))
}
return err
}
// initStorage initializes storage directories with a random serial.
@ -571,7 +579,7 @@ func (c *Config) UpdateHub() {
// initHub initializes PhotoPrism hub config.
func (c *Config) initHub() {
c.hub = hub.NewConfig(c.Version(), c.HubConfigFile(), c.serial)
c.hub = hub.NewConfig(c.Version(), c.HubConfigFile(), c.serial, c.options.PartnerID)
if err := c.hub.Load(); err == nil {
// Do nothing.

View file

@ -64,6 +64,12 @@ var GlobalFlags = []cli.Flag{
Usage: "enable experimental features",
EnvVar: "PHOTOPRISM_EXPERIMENTAL",
},
cli.StringFlag{
Name: "partner-id",
Hidden: true,
Usage: "hosting partner id",
EnvVar: "PHOTOPRISM_PARTNER_ID",
},
cli.StringFlag{
Name: "config-file, c",
Usage: "load config options from `FILENAME`",

View file

@ -26,15 +26,16 @@ const (
// Options provides a struct in which application configuration is stored.
// Application code must use functions to get config options, for two reasons:
//
// 1. Some options are computed and we don't want to leak implementation details (aims at reducing refactoring overhead).
//
// 2. Paths might actually be dynamic later (if we build a multi-user version).
// 1. We do not want to leak implementation details so refactoring overhead is kept low
// 2. Some config values are dynamically generated
// 3. Paths may become dynamic too at a later time
//
// See https://github.com/photoprism/photoprism/issues/50#issuecomment-433856358
type Options struct {
Name string `json:"-"`
Version string `json:"-"`
Copyright string `json:"-"`
PartnerID string `yaml:"-" json:"-" flag:"partner-id"`
AdminPassword string `yaml:"AdminPassword" json:"-" flag:"admin-password"`
LogLevel string `yaml:"LogLevel" json:"-" flag:"log-level"`
Debug bool `yaml:"Debug" json:"Debug" flag:"debug"`

View file

@ -1,5 +1,9 @@
package entity
import (
"time"
)
// CreateDefaultFixtures inserts default fixtures for test and production.
func CreateDefaultFixtures() {
CreateUnknownAddress()
@ -11,8 +15,10 @@ func CreateDefaultFixtures() {
CreateUnknownLens()
}
// ResetTestFixtures re-creates registered database tables and inserts test fixtures.
// ResetTestFixtures recreates database tables and test fixtures.
func ResetTestFixtures() {
start := time.Now()
Entities.Migrate(Db(), false)
Entities.WaitForMigration(Db())
Entities.Truncate(Db())
@ -20,4 +26,6 @@ func ResetTestFixtures() {
CreateDefaultFixtures()
CreateTestFixtures()
log.Debugf("entity: recreated test fixtures [%s]", time.Since(start))
}

View file

@ -1,7 +1,13 @@
package entity
import (
"time"
)
// MigrateDb creates database tables and inserts default fixtures as needed.
func MigrateDb(dropDeprecated, runFailed bool) {
start := time.Now()
if dropDeprecated {
DeprecatedTables.Drop(Db())
}
@ -10,6 +16,8 @@ func MigrateDb(dropDeprecated, runFailed bool) {
Entities.WaitForMigration(Db())
CreateDefaultFixtures()
log.Debugf("entity: successfully initialized [%s]", time.Since(start))
}
// InitTestDb connects to and completely initializes the test database incl fixtures.

View file

@ -31,10 +31,11 @@ type Config struct {
Version string `json:"version" yaml:"Version"`
Serial string `json:"serial" yaml:"Serial"`
FileName string `json:"-" yaml:"-"`
PartnerID string `json:"-" yaml:"-"`
}
// NewConfig creates a new backend api credentials instance.
func NewConfig(version, fileName, serial string) *Config {
func NewConfig(version, fileName, serial, partner string) *Config {
return &Config{
Key: "",
Secret: "",
@ -43,6 +44,7 @@ func NewConfig(version, fileName, serial string) *Config {
Version: version,
Serial: serial,
FileName: fileName,
PartnerID: partner,
}
}
@ -140,12 +142,12 @@ func (c *Config) Refresh() (err error) {
if c.Key != "" {
url = fmt.Sprintf(ServiceURL+"/%s", c.Key)
method = http.MethodPut
log.Debugf("getting updated api key for maps & places from %s", ApiHost())
log.Debugf("config: requesting updated api key for maps and places")
} else {
log.Debugf("requesting api key for maps & places from %s", ApiHost())
log.Debugf("config: requesting new api key for maps and places")
}
if j, err := json.Marshal(NewRequest(c.Version, c.Serial)); err != nil {
if j, err := json.Marshal(NewRequest(c.Version, c.Serial, c.PartnerID)); err != nil {
return err
} else if req, err = http.NewRequest(method, url, bytes.NewReader(j)); err != nil {
return err
@ -166,7 +168,7 @@ func (c *Config) Refresh() (err error) {
if err != nil {
return err
} else if r.StatusCode >= 400 {
err = fmt.Errorf("getting api key from %s failed (error %d)", ApiHost(), r.StatusCode)
err = fmt.Errorf("fetching api key from %s failed (error %d)", ApiHost(), r.StatusCode)
return err
}

View file

@ -8,7 +8,7 @@ import (
func TestConfig_MapKey(t *testing.T) {
t.Run("success", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9", "test")
assert.Equal(t, "", c.MapKey())
})
}

View file

@ -22,6 +22,7 @@ type Feedback struct {
UserEmail string `json:"UserEmail"`
UserAgent string `json:"UserAgent"`
ApiKey string `json:"ApiKey"`
PartnerID string `json:"PartnerID"`
ClientVersion string `json:"ClientVersion"`
ClientSerial string `json:"ClientSerial"`
ClientOS string `json:"ClientOS"`
@ -30,8 +31,9 @@ type Feedback struct {
}
// NewFeedback creates a new hub feedback instance.
func NewFeedback(version, serial string) *Feedback {
func NewFeedback(version, serial, partner string) *Feedback {
return &Feedback{
PartnerID: partner,
ClientVersion: version,
ClientSerial: serial,
ClientOS: runtime.GOOS,
@ -41,7 +43,7 @@ func NewFeedback(version, serial string) *Feedback {
}
func (c *Config) SendFeedback(f form.Feedback) (err error) {
feedback := NewFeedback(c.Version, c.Serial)
feedback := NewFeedback(c.Version, c.Serial, c.PartnerID)
feedback.Category = f.Category
feedback.Subject = txt.Shorten(f.Message, 50, "...")
feedback.Message = f.Message

View file

@ -9,7 +9,7 @@ import (
func TestNewFeedback(t *testing.T) {
t.Run("success", func(t *testing.T) {
feedback := NewFeedback("xxx", "zqkunt22r0bewti9")
feedback := NewFeedback("xxx", "zqkunt22r0bewti9", "test")
assert.Equal(t, "xxx", feedback.ClientVersion)
assert.Equal(t, "zqkunt22r0bewti9", feedback.ClientSerial)
})
@ -17,7 +17,7 @@ func TestNewFeedback(t *testing.T) {
func TestSendFeedback(t *testing.T) {
t.Run("success", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9", "test")
feedback := Feedback{
Category: "Bug Report",

View file

@ -49,13 +49,13 @@ func Token(size uint) string {
}
func TestNewConfig(t *testing.T) {
c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/new.yml", "zqkunt22r0bewti9", "test")
assert.IsType(t, &Config{}, c)
}
func TestNewRequest(t *testing.T) {
r := NewRequest("0.0.0", "zqkunt22r0bewti9")
r := NewRequest("0.0.0", "zqkunt22r0bewti9", "test")
assert.IsType(t, &Request{}, r)
@ -72,7 +72,7 @@ func TestConfig_Refresh(t *testing.T) {
t.Run("success", func(t *testing.T) {
fileName := fmt.Sprintf("testdata/hub.%s.yml", Token(8))
c := NewConfig("0.0.0", fileName, "zqkunt22r0bewti9")
c := NewConfig("0.0.0", fileName, "zqkunt22r0bewti9", "test")
if err := c.Refresh(); err != nil {
t.Fatal(err)
@ -122,7 +122,7 @@ func TestConfig_Refresh(t *testing.T) {
func TestConfig_DecodeSession(t *testing.T) {
t.Run("hub3.yml", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/hub3.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/hub3.yml", "zqkunt22r0bewti9", "test")
err := c.Load()
@ -138,7 +138,7 @@ func TestConfig_DecodeSession(t *testing.T) {
func TestConfig_Load(t *testing.T) {
t.Run("hub1.yml", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/hub1.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/hub1.yml", "zqkunt22r0bewti9", "test")
if err := c.Load(); err != nil {
t.Logf(err.Error())
@ -151,7 +151,7 @@ func TestConfig_Load(t *testing.T) {
assert.Equal(t, "0.0.0", c.Version)
})
t.Run("hub2.yml", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/hub2.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/hub2.yml", "zqkunt22r0bewti9", "test")
if err := c.Load(); err != nil {
t.Logf(err.Error())
@ -164,7 +164,7 @@ func TestConfig_Load(t *testing.T) {
assert.Equal(t, "200925-f8e2b580-Darwin-i386-DEBUG", c.Version)
})
t.Run("not existing filename", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/hub_xxx.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/hub_xxx.yml", "zqkunt22r0bewti9", "test")
if err := c.Load(); err == nil {
t.Fatal("file should not exist")
@ -180,7 +180,7 @@ func TestConfig_Save(t *testing.T) {
t.Run("existing filename", func(t *testing.T) {
assert.FileExists(t, "testdata/hub1.yml")
c := NewConfig("0.0.0", "testdata/hub1.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/hub1.yml", "zqkunt22r0bewti9", "test")
if err := c.Load(); err != nil {
t.Logf(err.Error())
@ -219,7 +219,7 @@ func TestConfig_Save(t *testing.T) {
assert.Equal(t, "0.0.0", c.Version)
})
t.Run("not existing filename", func(t *testing.T) {
c := NewConfig("0.0.0", "testdata/hub_new.yml", "zqkunt22r0bewti9")
c := NewConfig("0.0.0", "testdata/hub_new.yml", "zqkunt22r0bewti9", "test")
c.Key = "F60F5B25D59C397989E3CD374F81CDD7710A4FCA"
c.Secret = "foo"
c.Session = "bar"

View file

@ -9,6 +9,7 @@ var ServiceURL = "https://hub.photoprism.app/v1/hello"
// Request represents basic environment specs for debugging.
type Request struct {
PartnerID string `json:"PartnerID"`
ClientVersion string `json:"ClientVersion"`
ClientSerial string `json:"ClientSerial"`
ClientOS string `json:"ClientOS"`
@ -17,8 +18,9 @@ type Request struct {
}
// NewRequest creates a new backend key request instance.
func NewRequest(version, serial string) *Request {
func NewRequest(version, serial, partner string) *Request {
return &Request{
PartnerID: partner,
ClientVersion: version,
ClientSerial: serial,
ClientOS: runtime.GOOS,

View file

@ -17,9 +17,8 @@ type MigrationMap map[string]Migration
// Existing finds and returns previously executed database schema migrations.
func Existing(db *gorm.DB) MigrationMap {
result := make(MigrationMap)
dialect := db.Dialect().GetName()
stmt := db.Model(Migration{}).Where("dialect = ?", dialect)
stmt := db.Model(Migration{})
stmt = stmt.Select("id, dialect, error, source, started_at, finished_at")
rows, err := stmt.Rows()
@ -52,7 +51,11 @@ func (m *Migrations) Start(db *gorm.DB, runFailed bool) {
// Find previously executed migrations.
executed := Existing(db)
if prev := len(executed); prev == 0 {
log.Infof("migrate: found no previous migrations")
} else {
log.Debugf("migrate: found %s", english.Plural(len(executed), "previous migration", "previous migrations"))
}
for _, migration := range *m {
start := time.Now()

View file

@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"time"
"github.com/gin-contrib/gzip"
"github.com/gin-gonic/gin"
@ -21,6 +22,8 @@ func Start(ctx context.Context, conf *config.Config) {
}
}()
start := time.Now()
// Set HTTP server mode.
if conf.HttpMode() != "" {
gin.SetMode(conf.HttpMode())
@ -76,6 +79,8 @@ func Start(ctx context.Context, conf *config.Config) {
Handler: router,
}
log.Debugf("http: successfully initialized [%s]", time.Since(start))
// Start HTTP server.
go func() {
log.Infof("http: starting web server at %s", server.Addr)