diff --git a/mysql/provider.go b/mysql/provider.go index 256b6e617..4ded155d5 100644 --- a/mysql/provider.go +++ b/mysql/provider.go @@ -10,7 +10,6 @@ import ( "time" "github.com/go-sql-driver/mysql" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" @@ -172,26 +171,6 @@ func quoteIdentifier(in string) string { return fmt.Sprintf("`%s`", identQuoteReplacer.Replace(in)) } -func serverVersion(db *sql.DB) (*version.Version, error) { - var versionString string - err := db.QueryRow("SELECT @@GLOBAL.innodb_version").Scan(&versionString) - if err != nil { - return nil, err - } - - return version.NewVersion(versionString) -} - -func serverVersionString(db *sql.DB) (string, error) { - var versionString string - err := db.QueryRow("SELECT @@GLOBAL.version").Scan(&versionString) - if err != nil { - return "", err - } - - return versionString, nil -} - func connectToMySQL(conf *MySQLConfiguration) (*sql.DB, error) { dsn := conf.Config.FormatDSN() diff --git a/mysql/resource_database.go b/mysql/resource_database.go index 5f47e2d81..f1b8934ae 100644 --- a/mysql/resource_database.go +++ b/mysql/resource_database.go @@ -7,7 +7,6 @@ import ( "strings" "github.com/go-sql-driver/mysql" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -119,20 +118,13 @@ func ReadDatabase(d *schema.ResourceData, meta interface{}) error { stmtSQL := "SHOW COLLATION WHERE `Charset` = ? AND `Default` = 'Yes'" var empty interface{} - requiredVersion, _ := version.NewVersion("8.0.0") currentVersion, err := serverVersion(db) if err != nil { return err } - serverVersionString, err := serverVersionString(db) - if err != nil { - return err - } - - // MySQL 8 returns more data in a row. var res error - if !strings.Contains(serverVersionString, "MariaDB") && currentVersion.GreaterThan(requiredVersion) { + if currentVersion.extraColumnInShowCollation() { res = db.QueryRow(stmtSQL, defaultCharset).Scan(&defaultCollation, &empty, &empty, &empty, &empty, &empty, &empty) } else { res = db.QueryRow(stmtSQL, defaultCharset).Scan(&defaultCollation, &empty, &empty, &empty, &empty, &empty) diff --git a/mysql/resource_grant.go b/mysql/resource_grant.go index db9feb198..ee5e1dd6d 100644 --- a/mysql/resource_grant.go +++ b/mysql/resource_grant.go @@ -1,12 +1,10 @@ package mysql import ( - "database/sql" "fmt" "log" "strings" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -127,27 +125,17 @@ func userOrRole(user string, host string, role string, hasRoles bool) (string, b } } -func supportsRoles(db *sql.DB) (bool, error) { - currentVersion, err := serverVersion(db) - if err != nil { - return false, err - } - - requiredVersion, _ := version.NewVersion("8.0.0") - hasRoles := currentVersion.GreaterThan(requiredVersion) - return hasRoles, nil -} - func CreateGrant(d *schema.ResourceData, meta interface{}) error { db, err := connectToMySQL(meta.(*MySQLConfiguration)) if err != nil { return err } - hasRoles, err := supportsRoles(db) + serverVersion, err := serverVersion(db) if err != nil { return err } + hasRoles := serverVersion.supportsRoles() var ( privilegesOrRoles string @@ -223,10 +211,11 @@ func ReadGrant(d *schema.ResourceData, meta interface{}) error { return err } - hasRoles, err := supportsRoles(db) + serverVersion, err := serverVersion(db) if err != nil { return err } + hasRoles := serverVersion.supportsRoles() userOrRole, _, err := userOrRole( d.Get("user").(string), @@ -260,10 +249,11 @@ func DeleteGrant(d *schema.ResourceData, meta interface{}) error { table := formatTableName(d.Get("table").(string)) - hasRoles, err := supportsRoles(db) + serverVersion, err := serverVersion(db) if err != nil { return err } + hasRoles := serverVersion.supportsRoles() userOrRole, isRole, err := userOrRole( d.Get("user").(string), diff --git a/mysql/resource_grant_test.go b/mysql/resource_grant_test.go index 9f0f0116d..a2c0692bf 100644 --- a/mysql/resource_grant_test.go +++ b/mysql/resource_grant_test.go @@ -9,7 +9,6 @@ import ( "time" "github.com/go-sql-driver/mysql" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" ) @@ -58,14 +57,14 @@ func TestAccGrant_role(t *testing.T) { return } - requiredVersion, _ := version.NewVersion("8.0.0") - currentVersion, err := serverVersion(db) + serverVersion, err := serverVersion(db) if err != nil { return } + hasRoles := serverVersion.supportsRoles() - if currentVersion.LessThan(requiredVersion) { - t.Skip("Roles require MySQL 8+") + if !hasRoles { + t.Skip("Roles are not supported by this version") } }, Providers: testAccProviders, @@ -93,14 +92,14 @@ func TestAccGrant_roleToUser(t *testing.T) { return } - requiredVersion, _ := version.NewVersion("8.0.0") - currentVersion, err := serverVersion(db) + serverVersion, err := serverVersion(db) if err != nil { return } + hasRoles := serverVersion.supportsRoles() - if currentVersion.LessThan(requiredVersion) { - t.Skip("Roles require MySQL 8+") + if !hasRoles { + t.Skip("Roles are not supported by this version") } }, Providers: testAccProviders, diff --git a/mysql/resource_role_test.go b/mysql/resource_role_test.go index a6256f21b..b5a61b455 100644 --- a/mysql/resource_role_test.go +++ b/mysql/resource_role_test.go @@ -5,7 +5,6 @@ import ( "fmt" "testing" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/terraform" ) @@ -22,14 +21,14 @@ func TestAccRole_basic(t *testing.T) { return } - requiredVersion, _ := version.NewVersion("8.0.0") - currentVersion, err := serverVersion(db) + serverVersion, err := serverVersion(db) if err != nil { return } + hasRoles := serverVersion.supportsRoles() - if currentVersion.LessThan(requiredVersion) { - t.Skip("Roles require MySQL 8+") + if !hasRoles { + t.Skip("Roles are not supported by this version") } }, Providers: testAccProviders, diff --git a/mysql/resource_user.go b/mysql/resource_user.go index d72067ac9..188342fd3 100644 --- a/mysql/resource_user.go +++ b/mysql/resource_user.go @@ -6,7 +6,6 @@ import ( "errors" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -105,13 +104,12 @@ func CreateUser(d *schema.ResourceData, meta interface{}) error { stmtSQL = stmtSQL + fmt.Sprintf(" IDENTIFIED BY '%s'", password) } - requiredVersion, _ := version.NewVersion("5.7.0") currentVersion, err := serverVersion(db) if err != nil { return err } - if currentVersion.GreaterThan(requiredVersion) && d.Get("tls_option").(string) != "" { + if currentVersion.supportsTlsOption() && d.Get("tls_option").(string) != "" { stmtSQL += fmt.Sprintf(" REQUIRE %s", d.Get("tls_option").(string)) } @@ -155,20 +153,18 @@ func UpdateUser(d *schema.ResourceData, meta interface{}) error { if newpw != nil { var stmtSQL string - /* ALTER USER syntax introduced in MySQL 5.7.6 deprecates SET PASSWORD (GH-8230) */ serverVersion, err := serverVersion(db) if err != nil { return fmt.Errorf("Could not determine server version: %s", err) } - ver, _ := version.NewVersion("5.7.6") - if serverVersion.LessThan(ver) { - stmtSQL = fmt.Sprintf("SET PASSWORD FOR '%s'@'%s' = PASSWORD('%s')", + if serverVersion.deprecatedSetPassword() { + stmtSQL = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s'", d.Get("user").(string), d.Get("host").(string), newpw.(string)) } else { - stmtSQL = fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s'", + stmtSQL = fmt.Sprintf("SET PASSWORD FOR '%s'@'%s' = PASSWORD('%s')", d.Get("user").(string), d.Get("host").(string), newpw.(string)) @@ -181,13 +177,12 @@ func UpdateUser(d *schema.ResourceData, meta interface{}) error { } } - requiredVersion, _ := version.NewVersion("5.7.0") currentVersion, err := serverVersion(db) if err != nil { return err } - if d.HasChange("tls_option") && currentVersion.GreaterThan(requiredVersion) { + if currentVersion.supportsTlsOption() && d.HasChange("tls_option") { var stmtSQL string stmtSQL = fmt.Sprintf("ALTER USER '%s'@'%s' REQUIRE %s", diff --git a/mysql/resource_user_password.go b/mysql/resource_user_password.go index 077136bac..d76715321 100644 --- a/mysql/resource_user_password.go +++ b/mysql/resource_user_password.go @@ -4,7 +4,6 @@ import ( "fmt" "github.com/gofrs/uuid" - "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-plugin-sdk/helper/encryption" "github.com/hashicorp/terraform-plugin-sdk/helper/schema" ) @@ -67,14 +66,13 @@ func SetUserPassword(d *schema.ResourceData, meta interface{}) error { d.Set("key_fingerprint", fingerprint) d.Set("encrypted_password", encrypted) - requiredVersion, _ := version.NewVersion("8.0.0") currentVersion, err := serverVersion(db) if err != nil { return err } passSQL := fmt.Sprintf("'%s'", password) - if currentVersion.LessThan(requiredVersion) { + if currentVersion.requiresExplicitPassword() { passSQL = fmt.Sprintf("PASSWORD(%s)", passSQL) } diff --git a/mysql/resource_user_test.go b/mysql/resource_user_test.go index 0d444f22b..4d4368880 100644 --- a/mysql/resource_user_test.go +++ b/mysql/resource_user_test.go @@ -52,7 +52,22 @@ func TestAccUser_basic(t *testing.T) { func TestAccUser_auth(t *testing.T) { resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, + PreCheck: func() { + testAccPreCheck(t) + db, err := connectToMySQL(testAccProvider.Meta().(*MySQLConfiguration)) + if err != nil { + return + } + + serverVersion, err := serverVersion(db) + if err != nil { + return + } + + if serverVersion.vendor == MariaDB { + t.Skip("MariaDB does not support the mysql_no_login plugin") + } + }, Providers: testAccProviders, CheckDestroy: testAccUserCheckDestroy, Steps: []resource.TestStep{ diff --git a/mysql/versions.go b/mysql/versions.go new file mode 100644 index 000000000..04e3a34c9 --- /dev/null +++ b/mysql/versions.go @@ -0,0 +1,112 @@ +package mysql + +import ( + "database/sql" + "github.com/hashicorp/go-version" + "strings" +) + +type SQLVendor string + +const ( + MySQL SQLVendor = "MySQL" + MariaDB SQLVendor = "MariaDB" +) + +type ServerVersion struct { + vendor SQLVendor + version *version.Version +} + +func serverVersion(db *sql.DB) (ServerVersion, error) { + versionString, err := serverVersionString(db) + if err != nil { + return ServerVersion{}, err + } + + return parseServerVersionString(versionString), nil +} + +func (serverVersion ServerVersion) supportsRoles() bool { + var requiredVersion *version.Version + if serverVersion.vendor == MariaDB { + requiredVersion, _ = version.NewVersion("10.0.5") + } else { + requiredVersion, _ = version.NewVersion("8.0.0") + } + + return serverVersion.version.GreaterThanOrEqual(requiredVersion) +} + +// ALTER USER syntax introduced in MySQL 5.7.6 deprecates SET PASSWORD (GH-8230) +// We assume that MariaDB will eventually follow suit +func (serverVersion ServerVersion) deprecatedSetPassword() bool { + var requiredVersion *version.Version + if serverVersion.vendor == MariaDB { + requiredVersion, _ = version.NewVersion("10.2.0") + } else { + requiredVersion, _ = version.NewVersion("5.7.6") + } + + return serverVersion.version.GreaterThanOrEqual(requiredVersion) +} + +func (serverVersion ServerVersion) supportsTlsOption() bool { + var requiredVersion *version.Version + if serverVersion.vendor == MariaDB { + // TODO since when exactly has MariaDB had this option? + // The docs are unclear: https://mariadb.com/kb/en/create-user/#tls-options + requiredVersion, _ = version.NewVersion("10.2.0") + } else { + requiredVersion, _ = version.NewVersion("5.7.0") + } + + return serverVersion.version.GreaterThanOrEqual(requiredVersion) +} + +func (serverVersion ServerVersion) requiresExplicitPassword() bool { + notRequiredVersion, _ := version.NewVersion("8.0.0") + if serverVersion.vendor == MySQL && serverVersion.version.GreaterThanOrEqual(notRequiredVersion) { + return false + } + + return true +} + +// MySQL 8 returns more data in a single row when issuing +// a SHOW COLLATION statement +func (serverVersion ServerVersion) extraColumnInShowCollation() bool { + requiredMySQLVersion, _ := version.NewVersion("8.0.0") + if serverVersion.vendor == MySQL && serverVersion.version.GreaterThanOrEqual(requiredMySQLVersion) { + return true + } + + return false +} + +func parseServerVersionString(versionString string) ServerVersion { + parts := strings.Split(versionString, "-") + newVersion, _ := version.NewVersion(parts[0]) + + if len(parts) == 1 { + return ServerVersion{ + vendor: MySQL, + version: newVersion, + } + } else { + return ServerVersion{ + vendor: MariaDB, + version: newVersion, + } + } +} + +func serverVersionString(db *sql.DB) (string, error) { + var versionString string + err := db.QueryRow("SELECT @@GLOBAL.version").Scan(&versionString) + if err != nil { + return "", err + } + + return versionString, nil +} diff --git a/mysql/versions_test.go b/mysql/versions_test.go new file mode 100644 index 000000000..5a299a8ca --- /dev/null +++ b/mysql/versions_test.go @@ -0,0 +1,28 @@ +package mysql + +import ( + "github.com/hashicorp/go-version" + "testing" +) + +func TestParseServerVersionString_MySQL(t *testing.T) { + mysql := parseServerVersionString("5.7.5") + if mysql.vendor != MySQL { + t.Errorf("Simple versions are assumed to be MySQL") + } + expected, _ := version.NewVersion("5.7.5") + if !mysql.version.Equal(expected) { + t.Errorf("Should not modify the simple version string") + } +} + +func TestParseServerVersionString_MariaDB(t *testing.T) { + mariadb := parseServerVersionString("10.1.8-MariaDB") + if mariadb.vendor != MariaDB { + t.Errorf("MariaDB adds a postfix to their version strings") + } + expected, _ := version.NewVersion("10.1.8") + if !mariadb.version.Equal(expected) { + t.Errorf("Should get the correct part as version string") + } +}