diff --git a/applications/apply.go b/applications/apply.go new file mode 100644 index 0000000..952ca78 --- /dev/null +++ b/applications/apply.go @@ -0,0 +1,274 @@ +package applications + +import ( + "errors" + "fmt" + "io/ioutil" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/kenji-yamane/mgr8/domain" + "github.com/kenji-yamane/mgr8/infrastructure" +) + +type ApplyCommand interface { + +} + +type applyCommand struct { + driver domain.Driver + hashService HashService +} + +func NewApplyCommand(driver domain.Driver, hashService HashService) *applyCommand { + return &applyCommand{driver: driver, hashService: hashService} +} + +type Migrations struct { + files []infrastructure.MigrationFile + isUpType bool +} + +type ApplyCommandParameters struct { + MigrationsDir string + DatabaseURL string + NumMigrations int + MigrationType string +} + +func (a *applyCommand) Execute(parameters *ApplyCommandParameters) error { + migrationFiles, err := a.getMigrationsFiles(parameters.MigrationsDir) + if err != nil { + return err + } + + return a.driver.ExecuteTransaction(parameters.DatabaseURL, func() error { + err := CheckAndInstallTool(a.driver) + + version, err := a.driver.GetLatestMigrationVersion() + if err != nil { + return err + } + + migrationsToRun, err := a.getMigrationsToRun(migrationFiles, version, parameters.NumMigrations, parameters.MigrationType) + if err != nil { + return err + } + + _, err = a.runMigrations(migrationsToRun, version, a.driver) + if err != nil { + return err + } + + return err + }) +} + +// reads directory and returns an array containing full paths of files inside +func (a *applyCommand) getMigrationsFiles(dir string) ([]infrastructure.MigrationFile, error) { + migrationFiles := []infrastructure.MigrationFile{} + + dirPath, err := filepath.Abs(dir) + if err != nil { + return []infrastructure.MigrationFile{}, err + } + + fileInfos, err := ioutil.ReadDir(dir) + if err != nil { + return []infrastructure.MigrationFile{}, err + } + + for _, fileInfo := range fileInfos { + var migrationFile infrastructure.MigrationFile + migrationFile.Name = fileInfo.Name() + migrationFile.FullPath = filepath.Join(dirPath, fileInfo.Name()) + migrationFiles = append(migrationFiles, migrationFile) + } + + return migrationFiles, err +} + +// returns sorted migration files +// if migration of type up orders ascending, descending otherwise +func (a *applyCommand) sortMigrationFiles(files []infrastructure.MigrationFile, isUpType bool) []infrastructure.MigrationFile { + if isUpType { + // sort by ascending + sort.Slice(files, func(i, j int) bool { + iNum, _ := GetMigrationNumber(files[i].Name) + jNum, _ := GetMigrationNumber(files[j].Name) + return iNum < jNum + }) + } else { + // sort by descending + sort.Slice(files, func(i, j int) bool { + iNum, _ := GetMigrationNumber(files[i].Name) + jNum, _ := GetMigrationNumber(files[j].Name) + return iNum >= jNum + }) + } + return files +} + +// returns migrations files in folder that match type specified (up/down) +func (a *applyCommand) getMigrationsToRun(migrationFiles []infrastructure.MigrationFile, currentVersion int, numMigrations int, migrationType string) (Migrations, error) { + var migrations Migrations + + isUpType := migrationType == "up" + var files []infrastructure.MigrationFile + + migrationsToBeIncluded := map[int]bool{} + + var firstNum int + var lastNum int + + // set range of migrations + if isUpType { + firstNum = currentVersion + 1 + lastNum = currentVersion + numMigrations + } else { + firstNum = currentVersion - numMigrations + 1 + lastNum = currentVersion + } + + if firstNum <= 0 { + return migrations, errors.New("migrations would exceed current migration version") + } + + for i := firstNum; i <= lastNum; i++ { + migrationsToBeIncluded[i] = true + } + + for _, file := range migrationFiles { + migrationNum, err := GetMigrationNumber(file.Name) + if err != nil { + return migrations, err + } + + fileMigrationType, err := GetMigrationType(file.Name) + if err != nil { + return migrations, err + } + + shouldInclude, ok := migrationsToBeIncluded[migrationNum] + + if migrationType == fileMigrationType && ok && shouldInclude { + files = append(files, file) + migrationsToBeIncluded[migrationNum] = false + } + } + + // check if all migrations needed were found + for key, element := range migrationsToBeIncluded { + if element == true { + return migrations, errors.New("missing migration number " + strconv.Itoa(key)) + } + } + + files = a.sortMigrationFiles(files, isUpType) + + migrations.files = files + migrations.isUpType = isUpType + + return migrations, nil +} + +func (a *applyCommand) runMigrations(migrations Migrations, version int, driver domain.Driver) (int, error) { + usernameService := NewUserNameService() + username, err := usernameService.GetUserName() + if err != nil { + return 0, err + } + + migrationType := "up" + if !migrations.isUpType { + migrationType = "down" + } + + for _, file := range migrations.files { + migrationNum, err := GetMigrationNumber(file.Name) + if err != nil { + return 0, err + } + + currentDate := time.Now().Format("2006-01-02 15:04:05") + + hash, err := a.hashService.GetSqlHash(file.FullPath) + if err != nil { + return 0, err + } + + if migrations.isUpType { + if migrationNum == version+1 { + err = a.applyMigration(driver, file) + if err != nil { + return 0, err + } + + version = version + 1 + err = driver.InsertIntoMigrationLog(migrationNum, migrationType, username, currentDate) + if err != nil { + return 0, err + } + err = driver.InsertIntoAppliedMigrations(version, username, currentDate, hash) + if err != nil { + return 0, err + } + } else { + valid, err := a.hashService.ValidateFileMigration(migrationNum, file.FullPath, driver) + if err != nil { + return 0, err + } + if !valid { + return 0, fmt.Errorf("❌ invalid migration file %s", file.Name) + } + } + } else if !migrations.isUpType && migrationNum == version { + err = a.applyMigration(driver, file) + if err != nil { + return 0, err + } + + err = driver.InsertIntoMigrationLog(migrationNum, migrationType, username, currentDate) + if err != nil { + return 0, err + } + + err = driver.RemoveAppliedMigration(version) + if err != nil { + return 0, err + } + version = version - 1 + } + } + + return version, nil +} + +func (a *applyCommand) applyMigration(driver domain.Driver, migration infrastructure.MigrationFile) error { + fmt.Printf("Applying file %s\n", migration.Name) + content, err := os.ReadFile(migration.FullPath) + if err != nil { + return fmt.Errorf("could not read from file: %s", err) + } + + statements := FilterNonEmpty(strings.Split(string(content), ";")) + err = driver.Execute(statements) + if err != nil { + return fmt.Errorf("could not execute transaction: %s", err) + } + return nil +} + +func FilterNonEmpty(statements []string) []string { + filtered := make([]string, 0) + for _, s := range statements { + if strings.TrimSpace(s) != "" { + filtered = append(filtered, s) + } + } + return filtered +} diff --git a/applications/hashing.go b/applications/hashing.go index 92289c7..26ca24f 100755 --- a/applications/hashing.go +++ b/applications/hashing.go @@ -4,11 +4,13 @@ import ( "crypto/md5" "encoding/hex" + "github.com/kenji-yamane/mgr8/domain" "github.com/kenji-yamane/mgr8/infrastructure" ) type HashService interface { GetSqlHash(sqlFilePath string) (string, error) + ValidateFileMigration(version int, filePath string, driver domain.Driver) (bool, error) } type hashService struct { @@ -28,3 +30,17 @@ func (h *hashService) GetSqlHash(sqlFilePath string) (string, error) { string_hash_md5 := hex.EncodeToString(hash_md5[:]) return string_hash_md5, nil } + +func (h *hashService) ValidateFileMigration(version int, filePath string, driver domain.Driver) (bool, error) { + hashFile, err := h.GetSqlHash(filePath) + if err != nil { + return false, err + } + + hashDb, err := driver.GetVersionHashing(version) + if err != nil { + return false, err + } + + return hashFile == hashDb, nil +} diff --git a/cmd/apply.go b/cmd/apply.go index 751f4f0..e107d2b 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -2,70 +2,33 @@ package cmd import ( "errors" - "fmt" - "io/ioutil" - "os" - "path/filepath" - "sort" "strconv" - "strings" - "time" "github.com/kenji-yamane/mgr8/applications" "github.com/kenji-yamane/mgr8/domain" + "github.com/kenji-yamane/mgr8/infrastructure" ) -type apply struct { - hashService applications.HashService -} - -// TODO: replace file usage with infrastructure.FileService -type MigrationFile struct { - fullPath string - name string -} +type apply struct { } type CommandArgs struct { migrationType string numMigrations int } -type Migrations struct { - files []MigrationFile - isUpType bool -} - func (a *apply) execute(args []string, databaseURL string, migrationsDir string, driver domain.Driver) error { - dir := migrationsDir - migrationFiles, err := getMigrationsFiles(dir) - if err != nil { - return err - } - commandArgs, err := parseArgs(args) if err != nil { return err } - return driver.ExecuteTransaction(databaseURL, func() error { - err := applications.CheckAndInstallTool(driver) - - version, err := driver.GetLatestMigrationVersion() - if err != nil { - return err - } + applyCommand := applications.NewApplyCommand(driver, applications.NewHashService(infrastructure.NewFileService())) - migrationsToRun, err := getMigrationsToRun(migrationFiles, version, commandArgs.numMigrations, commandArgs.migrationType) - if err != nil { - return err - } - - _, err = a.runMigrations(migrationsToRun, version, driver) - if err != nil { - return err - } - - return err + return applyCommand.Execute(&applications.ApplyCommandParameters{ + MigrationsDir: migrationsDir, + DatabaseURL: databaseURL, + NumMigrations: commandArgs.numMigrations, + MigrationType: commandArgs.migrationType, }) } @@ -98,208 +61,3 @@ func parseArgs(args []string) (CommandArgs, error) { return commandArgs, nil } - -// reads directory and returns an array containing full paths of files inside -func getMigrationsFiles(dir string) ([]MigrationFile, error) { - migrationFiles := []MigrationFile{} - - dirPath, err := filepath.Abs(dir) - if err != nil { - return []MigrationFile{}, err - } - - fileInfos, err := ioutil.ReadDir(dir) - if err != nil { - return []MigrationFile{}, err - } - - for _, fileInfo := range fileInfos { - var migrationFile MigrationFile - migrationFile.name = fileInfo.Name() - migrationFile.fullPath = filepath.Join(dirPath, fileInfo.Name()) - migrationFiles = append(migrationFiles, migrationFile) - } - - return migrationFiles, err -} - -// returns sorted migration files -// if migration of type up orders ascending, descending otherwise -func sortMigrationFiles(files []MigrationFile, isUpType bool) []MigrationFile { - if isUpType { - // sort by ascending - sort.Slice(files, func(i, j int) bool { - iNum, _ := applications.GetMigrationNumber(files[i].name) - jNum, _ := applications.GetMigrationNumber(files[j].name) - return iNum < jNum - }) - } else { - // sort by descending - sort.Slice(files, func(i, j int) bool { - iNum, _ := applications.GetMigrationNumber(files[i].name) - jNum, _ := applications.GetMigrationNumber(files[j].name) - return iNum >= jNum - }) - } - return files -} - -// returns migrations files in folder that match type specified (up/down) -func getMigrationsToRun(migrationFiles []MigrationFile, currentVersion int, numMigrations int, migrationType string) (Migrations, error) { - var migrations Migrations - - isUpType := migrationType == "up" - var files []MigrationFile - - migrationsToBeIncluded := map[int]bool{} - - var firstNum int - var lastNum int - - // set range of migrations - if isUpType { - firstNum = currentVersion + 1 - lastNum = currentVersion + numMigrations - } else { - firstNum = currentVersion - numMigrations + 1 - lastNum = currentVersion - } - - if firstNum <= 0 { - return migrations, errors.New("migrations would exceed current migration version") - } - - for i := firstNum; i <= lastNum; i++ { - migrationsToBeIncluded[i] = true - } - - for _, file := range migrationFiles { - migrationNum, err := applications.GetMigrationNumber(file.name) - if err != nil { - return migrations, err - } - - fileMigrationType, err := applications.GetMigrationType(file.name) - if err != nil { - return migrations, err - } - - shouldInclude, ok := migrationsToBeIncluded[migrationNum] - - if migrationType == fileMigrationType && ok && shouldInclude { - files = append(files, file) - migrationsToBeIncluded[migrationNum] = false - } - } - - // check if all migrations needed were found - for key, element := range migrationsToBeIncluded { - if element == true { - return migrations, errors.New("missing migration number " + strconv.Itoa(key)) - } - } - - files = sortMigrationFiles(files, isUpType) - - migrations.files = files - migrations.isUpType = isUpType - - return migrations, nil -} - -func (a *apply) runMigrations(migrations Migrations, version int, driver domain.Driver) (int, error) { - username_service := applications.NewUserNameService() - username, err := username_service.GetUserName() - if err != nil { - return 0, err - } - - migrationType := "up" - if !migrations.isUpType { - migrationType = "down" - } - - for _, file := range migrations.files { - migrationNum, err := applications.GetMigrationNumber(file.name) - if err != nil { - return 0, err - } - - currentDate := time.Now().Format("2006-01-02 15:04:05") - - hash, err := a.hashService.GetSqlHash(file.fullPath) - if err != nil { - return 0, err - } - - if migrations.isUpType { - if migrationNum == version+1 { - err = a.applyMigration(driver, file) - if err != nil { - return 0, err - } - - version = version + 1 - err = driver.InsertIntoMigrationLog(migrationNum, migrationType, username, currentDate) - if err != nil { - return 0, err - } - err = driver.InsertIntoAppliedMigrations(version, username, currentDate, hash) - if err != nil { - return 0, err - } - } else { - valid, err := validateFileMigration(migrationNum, file.fullPath, driver, a.hashService) - if err != nil { - return 0, err - } - if !valid { - return 0, fmt.Errorf("❌ invalid migration file %s", file.name) - } - } - } else if !migrations.isUpType && migrationNum == version { - err = a.applyMigration(driver, file) - if err != nil { - return 0, err - } - - err = driver.InsertIntoMigrationLog(migrationNum, migrationType, username, currentDate) - if err != nil { - return 0, err - } - - err = driver.RemoveAppliedMigration(version) - if err != nil { - return 0, err - } - version = version - 1 - } - } - - return version, nil -} - -func (a *apply) applyMigration(driver domain.Driver, migration MigrationFile) error { - fmt.Printf("Applying file %s\n", migration.name) - content, err := os.ReadFile(migration.fullPath) - if err != nil { - return fmt.Errorf("could not read from file: %s", err) - } - - statements := FilterNonEmpty(strings.Split(string(content), ";")) - err = driver.Execute(statements) - if err != nil { - return fmt.Errorf("could not execute transaction: %s", err) - } - return nil -} - -func FilterNonEmpty(statements []string) []string { - filtered := make([]string, 0) - for _, s := range statements { - if strings.TrimSpace(s) != "" { - filtered = append(filtered, s) - } - } - return filtered -} diff --git a/cmd/validate.go b/cmd/validate.go index 49a6a94..224ea1c 100644 --- a/cmd/validate.go +++ b/cmd/validate.go @@ -38,7 +38,7 @@ func validateDirMigrations(dir string, driver domain.Driver, hashService applica return 0, err } - valid, err := validateFileMigration(version, fullName, driver, hashService) + valid, err := hashService.ValidateFileMigration(version, fullName, driver) if err != nil { return 0, err } @@ -52,17 +52,3 @@ func validateDirMigrations(dir string, driver domain.Driver, hashService applica return 0, nil } - -func validateFileMigration(version int, filePath string, driver domain.Driver, hashService applications.HashService) (bool, error) { - hash_file, err := hashService.GetSqlHash(filePath) - if err != nil { - return false, err - } - - hash_db, err := driver.GetVersionHashing(version) - if err != nil { - return false, err - } - - return hash_file == hash_db, nil -} diff --git a/go.mod b/go.mod index 87e4a4f..f793186 100644 --- a/go.mod +++ b/go.mod @@ -40,10 +40,8 @@ require ( github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect github.com/nxadm/tail v1.4.8 // indirect github.com/opentracing/opentracing-go v1.1.0 // indirect - github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect github.com/pingcap/failpoint v0.0.0-20210316064728-7acb0f0a3dfd // indirect github.com/pingcap/kvproto v0.0.0-20210806074406-317f69fb54b4 // indirect - github.com/pingcap/log v0.0.0-20210906054005-afc726e70354 // indirect github.com/pingcap/tipb v0.0.0-20211025074540-e1c7362eeeb4 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.5.1 // indirect @@ -51,8 +49,8 @@ require ( github.com/prometheus/common v0.9.1 // indirect github.com/prometheus/procfs v0.0.8 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect - github.com/shirou/gopsutil v3.21.3+incompatible // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/testify v1.7.1 // indirect github.com/tikv/client-go/v2 v2.0.0-alpha.0.20211221092530-2f48d74d6949 // indirect github.com/tikv/pd v1.1.0-beta.0.20210818112400-0c5667766690 // indirect github.com/uber/jaeger-client-go v2.22.1+incompatible // indirect @@ -60,11 +58,8 @@ require ( go.etcd.io/etcd v0.5.0-alpha.5.0.20200824191128-ae9734ed278b // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.7.0 // indirect - go.uber.org/zap v1.19.1 // indirect - golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect - golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect golang.org/x/text v0.3.7 // indirect google.golang.org/genproto v0.0.0-20200305110556-506484158171 // indirect google.golang.org/grpc v1.29.1 // indirect @@ -72,4 +67,14 @@ require ( gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.0 // indirect +) + +require ( + github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c // indirect + github.com/pingcap/log v0.0.0-20210906054005-afc726e70354 // indirect + github.com/shirou/gopsutil v3.21.3+incompatible // indirect + go.uber.org/zap v1.19.1 // indirect + golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838 // indirect + golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect ) diff --git a/go.sum b/go.sum index 43cbf89..e10794d 100644 --- a/go.sum +++ b/go.sum @@ -672,8 +672,9 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0/go.mod h1:qlH2+W7zXGZkczuL+r2nEBR2JTT+/lX05Nn6vPhc7OI= github.com/swaggo/http-swagger v0.0.0-20200308142732-58ac5e232fba/go.mod h1:O1lAbCgAAX/KZ80LM/OXwtWFI/5TvZlwxSg8Cq08PV0= @@ -889,8 +890,9 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -958,8 +960,9 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -1146,8 +1149,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.0.6/go.mod h1:KdrTanmfLPPyAOeYGyG+UpDys7/7eeWT1zCq+oekYnU= gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw= gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=