diff --git a/assets/local-beach/docker-compose.yml b/assets/local-beach/docker-compose.yml index 7b3da2d..f71b2f4 100644 --- a/assets/local-beach/docker-compose.yml +++ b/assets/local-beach/docker-compose.yml @@ -17,12 +17,12 @@ services: environment: - DEFAULT_HOST=hello.localbeach.net database: - image: mariadb:10.11 + image: mysql:8 container_name: local_beach_database networks: - local_beach volumes: - - {{databasePath}}:/var/lib/mysql + - {{mysqlDatabasePath}}:/var/lib/mysql healthcheck: test: "/usr/bin/mysql --user=root --password=password --execute \"SHOW DATABASES;\"" interval: 3s @@ -32,4 +32,6 @@ services: - MYSQL_ROOT_PASSWORD=password ports: - 3307:3306 - command: 'mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci' + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci diff --git a/assets/local-beach/mariadb-compose.yml b/assets/local-beach/mariadb-compose.yml new file mode 100644 index 0000000..58af4bd --- /dev/null +++ b/assets/local-beach/mariadb-compose.yml @@ -0,0 +1,42 @@ +networks: + local_beach: + external: true + +services: + database: + image: mysql:8 + container_name: local_beach_database + networks: + - local_beach + volumes: + - {{mysqlDatabasePath}}:/var/lib/mysql + healthcheck: + test: "/usr/bin/mysql --user=root --password=password --execute \"SHOW DATABASES;\"" + interval: 3s + timeout: 1s + retries: 10 + environment: + - MYSQL_ROOT_PASSWORD=password + ports: + - 3307:3306 + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + + mariadb: + image: mariadb:10.11 + container_name: local_beach_mariadb + networks: + - local_beach + volumes: + - {{mariadbDatabasePath}}:/var/lib/mysql + healthcheck: + test: "/usr/bin/mysql --user=root --password=password --execute \"SHOW DATABASES;\"" + interval: 3s + timeout: 1s + retries: 10 + environment: + - MYSQL_ROOT_PASSWORD=password + ports: + - 3306:3306 + command: 'mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci' diff --git a/cmd/beach/cmd/helpers.go b/cmd/beach/cmd/helpers.go index 0646859..c2038e0 100644 --- a/cmd/beach/cmd/helpers.go +++ b/cmd/beach/cmd/helpers.go @@ -136,6 +136,42 @@ func retrieveCloudStorageCredentials(instanceIdentifier string, projectNamespace return nil, bucketName, privateKey } +func writeLocalBeachComposeFile() { + composeFileContent := readFileFromAssets("local-beach/docker-compose.yml") + composeFileContent = strings.ReplaceAll(composeFileContent, "{{mysqlDatabasePath}}", path.MySQLDatabase) + composeFileContent = strings.ReplaceAll(composeFileContent, "{{certificatesPath}}", path.Certificates) + + destination, err := os.Create(filepath.Join(path.Base, "docker-compose.yml")) + if err != nil { + log.Error("failed creating docker-compose.yml: ", err) + } else { + _, err = destination.WriteString(composeFileContent) + if err != nil { + log.Error(err) + } + + } + _ = destination.Close() +} + +func writeMariaDBComposeFile() { + composeFileContent := readFileFromAssets("local-beach/mariadb-compose.yml") + composeFileContent = strings.ReplaceAll(composeFileContent, "{{mysqlDatabasePath}}", path.MySQLDatabase) + composeFileContent = strings.ReplaceAll(composeFileContent, "{{mariadbDatabasePath}}", path.MariaDBDatabase) + + destination, err := os.Create(filepath.Join(path.Base, "mariadb-compose.yml")) + if err != nil { + log.Error("failed creating mariadb-compose.yml: ", err) + } else { + _, err = destination.WriteString(composeFileContent) + if err != nil { + log.Error(err) + } + + } + _ = destination.Close() +} + func startLocalBeach() error { _, err := os.Stat(path.Base) if os.IsNotExist(err) { @@ -156,21 +192,7 @@ func startLocalBeach() error { } if len(nginxStatusOutput) == 0 || len(databaseStatusOutput) == 0 { - composeFileContent := readFileFromAssets("local-beach/docker-compose.yml") - composeFileContent = strings.ReplaceAll(composeFileContent, "{{databasePath}}", path.Database) - composeFileContent = strings.ReplaceAll(composeFileContent, "{{certificatesPath}}", path.Certificates) - - destination, err := os.Create(filepath.Join(path.Base, "docker-compose.yml")) - if err != nil { - log.Error("failed creating docker-compose.yml: ", err) - } else { - _, err = destination.WriteString(composeFileContent) - if err != nil { - log.Error(err) - } - - } - _ = destination.Close() + writeLocalBeachComposeFile() log.Info("Starting reverse proxy and database server ...") commandArgs := []string{"compose", "-f", filepath.Join(path.Base, "docker-compose.yml"), "up", "--remove-orphans", "-d"} diff --git a/cmd/beach/cmd/setup.go b/cmd/beach/cmd/setup.go index 0c249e8..7202065 100644 --- a/cmd/beach/cmd/setup.go +++ b/cmd/beach/cmd/setup.go @@ -15,14 +15,15 @@ package cmd import ( - "os" - "path/filepath" - "strings" - + "errors" "github.com/flownative/localbeach/pkg/exec" "github.com/flownative/localbeach/pkg/path" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "os" + "path/filepath" + "strings" + "time" ) // setupCmd represents the setup command @@ -66,7 +67,7 @@ func migrateOldBase() error { } log.Info("moving database data") - err = os.Rename(filepath.Join(path.OldBase, "MariaDB"), path.Database) + err = os.Rename(filepath.Join(path.OldBase, "MariaDB"), path.MariaDBDatabase) if err != nil { if os.IsNotExist(err) { log.Error(err) @@ -114,27 +115,113 @@ func setupLocalBeach() error { log.Error(err) } - log.Debug("creating directory for databases at " + path.Database) - err = os.MkdirAll(path.Database, os.ModePerm) + log.Debug("creating directory for databases at " + path.MySQLDatabase) + err = os.MkdirAll(path.MySQLDatabase, os.ModePerm) if err != nil && !os.IsExist(err) { log.Error(err) } - composeFileContent := readFileFromAssets("local-beach/docker-compose.yml") - composeFileContent = strings.ReplaceAll(composeFileContent, "{{databasePath}}", path.Database) - composeFileContent = strings.ReplaceAll(composeFileContent, "{{certificatesPath}}", path.Certificates) - - destination, err := os.Create(filepath.Join(path.Base, "docker-compose.yml")) + err = migrateMariaDBToMySQL() if err != nil { - log.Error("failed creating docker-compose.yml: ", err) - } else { - _, err = destination.WriteString(composeFileContent) + return err + } + + writeLocalBeachComposeFile() + + return nil +} + +func migrateMariaDBToMySQL() error { + _, err := os.Stat(path.MariaDBDatabase) + if err == nil { + log.Info("Migrating MariaDB data from " + path.MariaDBDatabase + " to MySQL at " + path.MySQLDatabase) + log.Warn("Note: This may take a while, depending on DB size!") + + if err = startMariaDB(); err != nil { + return err + } + + log.Debug("dumping data from MariaDB to MySQL") + commandArgs := []string{"exec", "local_beach_mariadb", "bash", "-c"} + commandArgs = append(commandArgs, "mysql -h local_beach_mariadb -u root -ppassword --batch --skip-column-names -e \"SHOW DATABASES;\" | grep -E -v \"(information|performance)_schema|mysql|sys\"") + databases, err := exec.RunCommand("docker", commandArgs) if err != nil { log.Error(err) + return err + } + + for _, database := range strings.Split(strings.TrimSuffix(databases, "\n"), "\n") { + log.Debug("… " + database) + commandArgs = []string{"exec", "local_beach_database", "bash", "-c"} + commandArgs = append(commandArgs, "mysqldump -h local_beach_mariadb -u root -ppassword --add-drop-trigger --compress --comments --dump-date --hex-blob --quote-names --routines --triggers --no-autocommit --no-tablespaces --skip-lock-tables --single-transaction --quick --databases "+database+" | sed -e \"s/DEFAULT '{}' COMMENT '(DC2Type:json)'/DEFAULT (JSON_OBJECT()) COMMENT '(DC2Type:json)'/\" | mysql -h local_beach_database -u root -ppassword") + _, err := exec.RunCommand("docker", commandArgs) + if err != nil { + log.Error(err) + } } + if err = stopMariaDB(); err != nil { + return err + } } - _ = destination.Close() + + log.Info("Done with migration to MySQL at " + path.MySQLDatabase) + log.Info("If all works as expected, remove " + path.MariaDBDatabase) return nil } + +func startMariaDB() error { + log.Debug("starting MariaDB server ...") + + writeMariaDBComposeFile() + + commandArgs := []string{"compose", "-f", filepath.Join(path.Base, "mariadb-compose.yml"), "up", "-d"} + err := exec.RunInteractiveCommand("docker", commandArgs) + if err != nil { + return errors.New("Database container startup failed") + } + + log.Debug("waiting for MariaDB server ...") + tries := 1 + for { + output, err := exec.RunCommand("docker", []string{"inspect", "-f", "{{.State.Health.Status}}", "local_beach_mariadb"}) + if err != nil { + return errors.New("failed to check for MariaDB server container health") + } + if strings.TrimSpace(output) == "healthy" { + break + } + if tries == 10 { + return errors.New("timeout waiting for MariaDB server to start") + } + tries++ + time.Sleep(3 * time.Second) + } + + log.Debug("waiting for MySQL server ...") + tries = 1 + for { + output, err := exec.RunCommand("docker", []string{"inspect", "-f", "{{.State.Health.Status}}", "local_beach_database"}) + if err != nil { + return errors.New("failed to check for MySQL server container health") + } + if strings.TrimSpace(output) == "healthy" { + break + } + if tries == 10 { + return errors.New("timeout waiting for MySQL server to start") + } + tries++ + time.Sleep(3 * time.Second) + } + + return nil +} +func stopMariaDB() error { + log.Debug("stopping MariaDB server ...") + commandArgs := []string{"compose", "-f", filepath.Join(path.Base, "mariadb-compose.yml"), "rm", "--force", "--stop", "-v"} + _, err := exec.RunCommand("docker", commandArgs) + + return err +} diff --git a/pkg/path/path_darwin.go b/pkg/path/path_darwin.go index 427ab29..88e73de 100644 --- a/pkg/path/path_darwin.go +++ b/pkg/path/path_darwin.go @@ -27,7 +27,8 @@ import ( var OldBase = "" var Base = "" var Certificates = "" -var Database = "" +var MariaDBDatabase = "" +var MySQLDatabase = "" func init() { homeDir, err := os.UserHomeDir() @@ -40,5 +41,6 @@ func init() { OldBase = filepath.Join(homeDir, "Library", "Application Support", "Flownative", "Local Beach") Base = filepath.Join(homeDir, ".LocalBeach") Certificates = filepath.Join(Base, "Certificates") - Database = filepath.Join(Base, "MariaDB") + MariaDBDatabase = filepath.Join(Base, "MariaDB") + MySQLDatabase = filepath.Join(Base, "MySQL") } diff --git a/pkg/path/path_linux.go b/pkg/path/path_linux.go index bb1049a..0c8d12e 100644 --- a/pkg/path/path_linux.go +++ b/pkg/path/path_linux.go @@ -27,7 +27,8 @@ import ( var OldBase = "" var Base = "" var Certificates = "" -var Database = "" +var MariaDBDatabase = "" +var MySQLDatabase = "" func init() { homeDir, err := os.UserHomeDir() @@ -40,5 +41,6 @@ func init() { OldBase = filepath.Join(homeDir, ".Flownative", "Local Beach") Base = filepath.Join(homeDir, ".LocalBeach") Certificates = filepath.Join(Base, "Certificates") - Database = filepath.Join(Base, "MariaDB") + MariaDBDatabase = filepath.Join(Base, "MariaDB") + MySQLDatabase = filepath.Join(Base, "MySQL") }