Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions cmd/magebox/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,17 +631,22 @@ func checkDatabaseConnection(p *platform.Platform, cfg *config.Config) checkResu

var serviceName string
var port string
var dbType, dbVersion string

if cfg.Services.HasMySQL() {
serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(cfg.Services.MySQL.Version, ".", ""))
switch cfg.Services.MySQL.Version {
dbType = "mysql"
dbVersion = cfg.Services.MySQL.Version
serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(dbVersion, ".", ""))
switch dbVersion {
case "8.4":
port = "33084"
default:
port = "33080"
}
} else if cfg.Services.HasMariaDB() {
serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(cfg.Services.MariaDB.Version, ".", ""))
dbType = "mariadb"
dbVersion = cfg.Services.MariaDB.Version
serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(dbVersion, ".", ""))
port = "33106"
}

Expand All @@ -658,7 +663,7 @@ func checkDatabaseConnection(p *platform.Platform, cfg *config.Config) checkResu

// Check if database exists
dockerCtrl := docker.NewDockerController(composeFile)
if dockerCtrl.DatabaseExists(serviceName, cfg.Name) {
if dockerCtrl.DatabaseExists(serviceName, cfg.Name, docker.DBClientBin(dbType, dbVersion)) {
return checkResult{
name: "Database",
status: "ok",
Expand Down
50 changes: 37 additions & 13 deletions cmd/magebox/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,30 @@ type dbInfo struct {
Port int // e.g., 33080
}

// isMariaDB11Plus returns true when this is a MariaDB 11.0 or newer container,
// which removed the mysql/mysqldump/mysqladmin symlinks.
func (d *dbInfo) isMariaDB11Plus() bool {
return docker.DBClientBin(d.Type, d.Version) == "mariadb"
}

// clientBin returns the SQL client binary name for this container.
func (d *dbInfo) clientBin() string {
return docker.DBClientBin(d.Type, d.Version)
}

// dumpBin returns the dump binary name for this container.
func (d *dbInfo) dumpBin() string {
return docker.DBDumpBin(d.Type, d.Version)
}

// adminBin returns the admin binary name for this container.
func (d *dbInfo) adminBin() string {
if d.isMariaDB11Plus() {
return "mariadb-admin"
}
return "mysqladmin"
}

// getDbInfo extracts database connection info from project config
func getDbInfo(cfg *config.Config) (*dbInfo, error) {
if cfg.Services.MySQL != nil && cfg.Services.MySQL.Enabled {
Expand Down Expand Up @@ -216,7 +240,7 @@ func runDbImport(cmd *cobra.Command, args []string) error {

// Create database if it doesn't exist
createCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName))
createCmd.Stderr = os.Stderr
if err := createCmd.Run(); err != nil {
Expand All @@ -242,7 +266,7 @@ func runDbImport(cmd *cobra.Command, args []string) error {

// Use docker exec directly with container name
importCmd := exec.Command("docker", "exec", "-i", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, dbName)
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, dbName)

// Handle gzip compressed files
if strings.HasSuffix(sqlFile, ".gz") {
Expand Down Expand Up @@ -311,7 +335,7 @@ func runDbExport(cmd *cobra.Command, args []string) error {
// Use docker exec directly with container name
// --no-tablespaces: Skip TABLESPACE statements (avoids permission issues on import)
exportCmd := exec.Command("docker", "exec", db.ContainerName,
"mysqldump", "-uroot", "-p"+docker.DefaultDBRootPassword, "--no-tablespaces", dbName)
db.dumpBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "--no-tablespaces", dbName)

file, err := os.Create(outputFile)
if err != nil {
Expand Down Expand Up @@ -352,7 +376,7 @@ func runDbShell(cmd *cobra.Command, args []string) error {

// Use docker exec directly with container name
shellCmd := exec.Command("docker", "exec", "-it", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, dbName)
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, dbName)
shellCmd.Stdin = os.Stdin
shellCmd.Stdout = os.Stdout
shellCmd.Stderr = os.Stderr
Expand Down Expand Up @@ -385,7 +409,7 @@ func runDbCreate(cmd *cobra.Command, args []string) error {

// Check if database already exists
checkCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME = '%s'", dbName))
output, err := checkCmd.Output()
if err == nil && strings.Contains(string(output), dbName) {
Expand All @@ -396,7 +420,7 @@ func runDbCreate(cmd *cobra.Command, args []string) error {
// Create database
fmt.Print("Creating database... ")
createCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName))
createCmd.Stderr = os.Stderr

Expand Down Expand Up @@ -447,7 +471,7 @@ func runDbDrop(cmd *cobra.Command, args []string) error {
fmt.Println()
fmt.Print("Dropping database... ")
dropCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", dbName))
dropCmd.Stderr = os.Stderr

Expand Down Expand Up @@ -500,7 +524,7 @@ func runDbReset(cmd *cobra.Command, args []string) error {
// Drop database
fmt.Print("Dropping database... ")
dropCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", dbName))
dropCmd.Stderr = os.Stderr

Expand All @@ -513,7 +537,7 @@ func runDbReset(cmd *cobra.Command, args []string) error {
// Create database
fmt.Print("Creating database... ")
createCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName))
createCmd.Stderr = os.Stderr

Expand Down Expand Up @@ -568,7 +592,7 @@ func runDbTop(cmd *cobra.Command, args []string) error {
fmt.Printf("Monitoring %s (Ctrl+C to stop)\n\n", cli.Highlight(db.ContainerName))

topCmd := exec.Command("docker", "exec", "-it", db.ContainerName,
"mysqladmin", "-uroot", "-p"+docker.DefaultDBRootPassword,
db.adminBin(), "-uroot", "-p"+docker.DefaultDBRootPassword,
"processlist", "--sleep=2", "--verbose")
topCmd.Stdin = os.Stdin
topCmd.Stdout = os.Stdout
Expand Down Expand Up @@ -640,7 +664,7 @@ func runDbSnapshotCreate(cmd *cobra.Command, args []string) error {

// Create gzipped dump
dumpCmd := exec.Command("docker", "exec", db.ContainerName,
"mysqldump", "-uroot", "-p"+docker.DefaultDBRootPassword,
db.dumpBin(), "-uroot", "-p"+docker.DefaultDBRootPassword,
"--no-tablespaces", "--single-transaction", dbName)

// Create output file with gzip compression
Expand Down Expand Up @@ -728,7 +752,7 @@ func runDbSnapshotRestore(cmd *cobra.Command, args []string) error {
// Drop and recreate database
fmt.Print("Resetting database... ")
resetCmd := exec.Command("docker", "exec", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, "-e",
fmt.Sprintf("DROP DATABASE IF EXISTS `%s`; CREATE DATABASE `%s` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci", dbName, dbName))
resetCmd.Stderr = os.Stderr
if err := resetCmd.Run(); err != nil {
Expand Down Expand Up @@ -757,7 +781,7 @@ func runDbSnapshotRestore(cmd *cobra.Command, args []string) error {

// Import into database
importCmd := exec.Command("docker", "exec", "-i", db.ContainerName,
"mysql", "-uroot", "-p"+docker.DefaultDBRootPassword, dbName)
db.clientBin(), "-uroot", "-p"+docker.DefaultDBRootPassword, dbName)
importCmd.Stdin = gzReader
importCmd.Stderr = os.Stderr

Expand Down
31 changes: 27 additions & 4 deletions internal/docker/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,29 @@ const (
DefaultRabbitMQPass = "guest"
)

// DBClientBin returns the SQL client binary name for the given database type and version.
// MariaDB 11.0+ removed the mysql/mysqldump/mysqladmin symlinks; use the native binary names instead.
func DBClientBin(dbType, version string) string {
if dbType == "mariadb" {
parts := strings.SplitN(version, ".", 2)
if major, err := strconv.Atoi(parts[0]); err == nil && major >= 11 {
return "mariadb"
}
}
return "mysql"
}

// DBDumpBin returns the dump binary name for the given database type and version.
func DBDumpBin(dbType, version string) string {
if dbType == "mariadb" {
parts := strings.SplitN(version, ".", 2)
if major, err := strconv.Atoi(parts[0]); err == nil && major >= 11 {
return "mariadb-dump"
}
}
return "mysqldump"
}

// ComposeGenerator generates Docker Compose configurations for global services
type ComposeGenerator struct {
platform *platform.Platform
Expand Down Expand Up @@ -1011,16 +1034,16 @@ func (c *DockerController) ExecSilent(serviceName string, command ...string) err
}

// CreateDatabase creates a database in the MySQL/MariaDB service
func (c *DockerController) CreateDatabase(serviceName, dbName string) error {
func (c *DockerController) CreateDatabase(serviceName, dbName, clientBin string) error {
cmd := buildComposeCmd(c.composeFile, "exec", "-T", serviceName,
"mysql", "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", dbName))
clientBin, "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", dbName))
return cmd.Run()
}

// DatabaseExists checks if a database exists
func (c *DockerController) DatabaseExists(serviceName, dbName string) bool {
func (c *DockerController) DatabaseExists(serviceName, dbName, clientBin string) bool {
cmd := buildComposeCmd(c.composeFile, "exec", "-T", serviceName,
"mysql", "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("SHOW DATABASES LIKE '%s'", dbName))
clientBin, "-uroot", "-p"+DefaultDBRootPassword, "-e", fmt.Sprintf("SHOW DATABASES LIKE '%s'", dbName))
output, err := cmd.Output()
return err == nil && strings.Contains(string(output), dbName)
}
Expand Down
47 changes: 47 additions & 0 deletions internal/docker/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1391,3 +1391,50 @@ func TestGenerateGlobalServices_SharedSearchContainer(t *testing.T) {
t.Errorf("elasticsearch Ports = %v, want [9500:9200]", es.Ports)
}
}

func TestDBClientBin(t *testing.T) {
tests := []struct {
dbType string
version string
want string
}{
{"mysql", "8.0", "mysql"},
{"mysql", "8.4", "mysql"},
{"mysql", "5.7", "mysql"},
{"mariadb", "10.4", "mysql"},
{"mariadb", "10.6", "mysql"},
{"mariadb", "10.11", "mysql"},
{"mariadb", "11.0", "mariadb"},
{"mariadb", "11.4", "mariadb"},
{"mariadb", "12.0", "mariadb"},
}
for _, tt := range tests {
t.Run(tt.dbType+"-"+tt.version, func(t *testing.T) {
got := DBClientBin(tt.dbType, tt.version)
if got != tt.want {
t.Errorf("DBClientBin(%q, %q) = %q, want %q", tt.dbType, tt.version, got, tt.want)
}
})
}
}

func TestDBDumpBin(t *testing.T) {
tests := []struct {
dbType string
version string
want string
}{
{"mysql", "8.0", "mysqldump"},
{"mariadb", "10.6", "mysqldump"},
{"mariadb", "11.0", "mariadb-dump"},
{"mariadb", "11.4", "mariadb-dump"},
}
for _, tt := range tests {
t.Run(tt.dbType+"-"+tt.version, func(t *testing.T) {
got := DBDumpBin(tt.dbType, tt.version)
if got != tt.want {
t.Errorf("DBDumpBin(%q, %q) = %q, want %q", tt.dbType, tt.version, got, tt.want)
}
})
}
}
12 changes: 8 additions & 4 deletions internal/project/lifecycle.go
Original file line number Diff line number Diff line change
Expand Up @@ -518,11 +518,15 @@ func (m *Manager) ensureDatabase(cfg *config.Config) error {
dockerController := docker.NewDockerController(m.composeGen.ComposeFilePath())

// Determine service name (version dots are removed in docker-compose service names)
var serviceName string
var serviceName, dbType, dbVersion string
if cfg.Services.HasMySQL() {
serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(cfg.Services.MySQL.Version, ".", ""))
dbType = "mysql"
dbVersion = cfg.Services.MySQL.Version
serviceName = fmt.Sprintf("mysql%s", strings.ReplaceAll(dbVersion, ".", ""))
} else if cfg.Services.HasMariaDB() {
serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(cfg.Services.MariaDB.Version, ".", ""))
dbType = "mariadb"
dbVersion = cfg.Services.MariaDB.Version
serviceName = fmt.Sprintf("mariadb%s", strings.ReplaceAll(dbVersion, ".", ""))
}

if serviceName == "" {
Expand All @@ -535,7 +539,7 @@ func (m *Manager) ensureDatabase(cfg *config.Config) error {
}

// Create database (use sanitized name - hyphens replaced with underscores)
return dockerController.CreateDatabase(serviceName, cfg.DatabaseName())
return dockerController.CreateDatabase(serviceName, cfg.DatabaseName(), docker.DBClientBin(dbType, dbVersion))
}

// getStartedServices returns a list of started service names
Expand Down
Loading