This is an automated email from the ASF dual-hosted git repository.

zhangliang2022 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-devlake.git


The following commit(s) were added to refs/heads/main by this push:
     new 06347adc Db Migration confirmation (#2500)
06347adc is described below

commit 06347adc64f4ae44158da0f186191f92e2a0792c
Author: Klesh Wong <[email protected]>
AuthorDate: Fri Jul 15 15:35:16 2022 +0800

    Db Migration confirmation (#2500)
    
    * refactor: remove swag from `make dev`
    
    * feat: migration confirmation
    
    * feat: execute migration withut confirmation when FORCE_MIGRATION=true
---
 Makefile              |  2 +-
 api/api.go            | 33 +++++++++++++++++++++++++++++++++
 main.go               |  2 --
 migration/migrator.go | 49 +++++++++++++++++++++++++++----------------------
 services/init.go      | 27 +++++++++++++++++++++++----
 5 files changed, 84 insertions(+), 29 deletions(-)

diff --git a/Makefile b/Makefile
index 7fc86054..b685adff 100644
--- a/Makefile
+++ b/Makefile
@@ -48,7 +48,7 @@ swag:
        swag init --parseDependency --parseInternal -o ./api/docs -g 
./api/api.go -g plugins/*/api/*.go
        echo "visit the swagger document on 
http://localhost:8080/swagger/index.html";
 
-dev: build-plugin swag run
+dev: build-plugin run
 
 debug: build-plugin-debug
        dlv debug main.go
diff --git a/api/api.go b/api/api.go
index 26443e45..2d1994fb 100644
--- a/api/api.go
+++ b/api/api.go
@@ -18,12 +18,16 @@ limitations under the License.
 package api
 
 import (
+       "fmt"
+       "net/http"
        "time"
 
        _ "github.com/apache/incubator-devlake/api/docs"
+       "github.com/apache/incubator-devlake/api/shared"
        "github.com/apache/incubator-devlake/logger"
 
        "github.com/apache/incubator-devlake/config"
+       "github.com/apache/incubator-devlake/services"
        "github.com/gin-contrib/cors"
        "github.com/gin-gonic/gin"
        ginSwagger "github.com/swaggo/gin-swagger"
@@ -38,10 +42,39 @@ import (
 // @host localhost:8080
 // @BasePath /
 func CreateApiService() {
+       services.Init()
        v := config.GetConfig()
        gin.SetMode(v.GetString("MODE"))
        router := gin.Default()
 
+       // Wait for user confirmation if db migration is needed
+       router.GET("/proceed-db-migration", func(ctx *gin.Context) {
+               if !services.MigrationRequireConfirmation() {
+                       shared.ApiOutputError(ctx, fmt.Errorf("no pending 
migration"), http.StatusBadRequest)
+                       return
+               }
+               err := services.ExecuteMigration()
+               if err != nil {
+                       shared.ApiOutputError(ctx, err, http.StatusBadRequest)
+                       return
+               }
+               shared.ApiOutputSuccess(ctx, nil, http.StatusOK)
+       })
+       router.Use(func(ctx *gin.Context) {
+               if !services.MigrationRequireConfirmation() {
+                       return
+               }
+               shared.ApiOutputError(
+                       ctx,
+                       fmt.Errorf("Database migration is required for Apache 
DevLake to function properly, it might cause the "+
+                               "collected data gets wiped out for consistency. 
Please send a request to `/proceed-migrations` "+
+                               "if it is ok, or you may downgrade back to the 
older version you previous used"),
+                       http.StatusPreconditionRequired,
+               )
+               ctx.Abort()
+               return
+       })
+
        router.GET("/swagger/*any", 
ginSwagger.WrapHandler(swaggerFiles.Handler))
 
        //endpoint debug log
diff --git a/main.go b/main.go
index 2ffe728d..7c3001f7 100644
--- a/main.go
+++ b/main.go
@@ -21,7 +21,6 @@ import (
        "github.com/apache/incubator-devlake/api"
        "github.com/apache/incubator-devlake/config"
        "github.com/apache/incubator-devlake/plugins/core"
-       "github.com/apache/incubator-devlake/services"
        _ "github.com/apache/incubator-devlake/version"
 )
 
@@ -37,6 +36,5 @@ func main() {
                        panic(err)
                }
        }
-       services.Init()
        api.CreateApiService()
 }
diff --git a/migration/migrator.go b/migration/migrator.go
index b143284a..ce7eae0f 100644
--- a/migration/migrator.go
+++ b/migration/migrator.go
@@ -25,7 +25,7 @@ import (
        "sync"
 )
 
-var m = migrator{scripts: make(map[string]scriptWithComment)}
+var m = migrator{}
 
 type scriptWithComment struct {
        Script
@@ -34,25 +34,37 @@ type scriptWithComment struct {
 type migrator struct {
        sync.Mutex
        db      *gorm.DB
-       scripts map[string]scriptWithComment
+       executed map[string]bool
+       scripts []*scriptWithComment
+       pending []*scriptWithComment
 }
 
 func Init(db *gorm.DB) {
        m.db = db
+       var err error
+       m.executed, err = m.getExecuted()
+       if err != nil {
+               panic(err)
+       }
 }
 
 func (m *migrator) register(scripts []Script, comment string) {
        m.Lock()
        defer m.Unlock()
        for _, script := range scripts {
-               m.scripts[fmt.Sprintf("%s:%d", script.Name(), 
script.Version())] = scriptWithComment{
+               key := fmt.Sprintf("%s:%d", script.Name(), script.Version())
+               swc := &scriptWithComment{
                        Script:  script,
                        comment: comment,
                }
+               m.scripts = append(m.scripts, swc)
+               if !m.executed[key] {
+                       m.pending = append(m.pending, swc)
+               }
        }
 }
 
-func (m *migrator) bookKeep(script scriptWithComment) error {
+func (m *migrator) bookKeep(script *scriptWithComment) error {
        record := &MigrationHistory{
                ScriptVersion: script.Version(),
                ScriptName:    script.Name(),
@@ -62,22 +74,11 @@ func (m *migrator) bookKeep(script scriptWithComment) error 
{
 }
 
 func (m *migrator) execute(ctx context.Context) error {
-       versions, err := m.getExecuted()
-       if err != nil {
-               return err
-       }
-       for key := range versions {
-               delete(m.scripts, key)
-       }
-       var scriptSlice []scriptWithComment
-       for _, script := range m.scripts {
-               scriptSlice = append(scriptSlice, script)
-       }
-       sort.Slice(scriptSlice, func(i, j int) bool {
-               return scriptSlice[i].Version() < scriptSlice[j].Version()
+       sort.Slice(m.pending, func(i, j int) bool {
+               return m.pending[i].Version() < m.pending[j].Version()
        })
-       for _, script := range scriptSlice {
-               err = script.Up(ctx, m.db)
+       for _, script := range m.pending {
+               err := script.Up(ctx, m.db)
                if err != nil {
                        return err
                }
@@ -88,9 +89,9 @@ func (m *migrator) execute(ctx context.Context) error {
        }
        return nil
 }
-func (m *migrator) getExecuted() (map[string]struct{}, error) {
+func (m *migrator) getExecuted() (map[string]bool, error) {
        var err error
-       versions := make(map[string]struct{})
+       versions := make(map[string]bool)
        err = m.db.Migrator().AutoMigrate(&MigrationHistory{})
        if err != nil {
                return nil, err
@@ -101,7 +102,7 @@ func (m *migrator) getExecuted() (map[string]struct{}, 
error) {
                return nil, err
        }
        for _, record := range records {
-               versions[fmt.Sprintf("%s:%d", record.ScriptName, 
record.ScriptVersion)] = struct{}{}
+               versions[fmt.Sprintf("%s:%d", record.ScriptName, 
record.ScriptVersion)] = true
        }
        return versions, nil
 }
@@ -113,3 +114,7 @@ func Register(scripts []Script, comment string) {
 func Execute(ctx context.Context) error {
        return m.execute(ctx)
 }
+
+func NeedConfirmation() bool {
+       return len(m.executed) > 0 && len(m.pending) > 0
+}
diff --git a/services/init.go b/services/init.go
index 072eda7d..be3610db 100644
--- a/services/init.go
+++ b/services/init.go
@@ -38,8 +38,9 @@ var cfg *viper.Viper
 var db *gorm.DB
 var cronManager *cron.Cron
 var log core.Logger
+var migrationRequireConfirmation bool
 
-// Init FIXME ...
+// Init the services module
 func Init() {
        var err error
        cfg = config.GetConfig()
@@ -62,11 +63,29 @@ func Init() {
        if err != nil {
                panic(err)
        }
-       err = migration.Execute(context.Background())
-       if err != nil {
-               panic(err)
+       forceMigration := cfg.GetBool("FORCE_MIGRATION")
+       if !migration.NeedConfirmation() || forceMigration {
+               err = ExecuteMigration()
+               if err != nil {
+                       panic(err)
+               }
+       } else {
+               migrationRequireConfirmation = true
        }
+       log.Info("Db migration confirmation needed: %v", 
migrationRequireConfirmation)
+}
 
+func ExecuteMigration() error {
+       err := migration.Execute(context.Background())
+       if err != nil {
+               return err
+       }
        // call service init
        pipelineServiceInit()
+       migrationRequireConfirmation = false
+       return nil
+}
+
+func MigrationRequireConfirmation() bool {
+       return migrationRequireConfirmation
 }

Reply via email to