forked from petoju/terraform-provider-mysql
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
provider/mysql: user and grant resources (#7656)
* provider/mysql: User Resource This commit introduces a mysql_user resource. It includes basic functionality of adding a user@host along with a password. * provider/mysql: Grant Resource This commit introduces a mysql_grant resource. It can grant a set of privileges to a user against a whole database. * provider/mysql: Adding documentation for user and grant resources
- Loading branch information
Showing
5 changed files
with
441 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package mysql | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strings" | ||
|
||
mysqlc "github.com/ziutek/mymysql/mysql" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
) | ||
|
||
func resourceGrant() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: CreateGrant, | ||
Update: nil, | ||
Read: ReadGrant, | ||
Delete: DeleteGrant, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"user": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"host": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
Default: "localhost", | ||
}, | ||
|
||
"database": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"privileges": &schema.Schema{ | ||
Type: schema.TypeSet, | ||
Required: true, | ||
ForceNew: true, | ||
Elem: &schema.Schema{Type: schema.TypeString}, | ||
Set: schema.HashString, | ||
}, | ||
|
||
"grant": &schema.Schema{ | ||
Type: schema.TypeBool, | ||
Optional: true, | ||
ForceNew: true, | ||
Default: false, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func CreateGrant(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(mysqlc.Conn) | ||
|
||
// create a comma-delimited string of privileges | ||
var privileges string | ||
var privilegesList []string | ||
vL := d.Get("privileges").(*schema.Set).List() | ||
for _, v := range vL { | ||
privilegesList = append(privilegesList, v.(string)) | ||
} | ||
privileges = strings.Join(privilegesList, ",") | ||
|
||
stmtSQL := fmt.Sprintf("GRANT %s on %s.* TO '%s'@'%s'", | ||
privileges, | ||
d.Get("database").(string), | ||
d.Get("user").(string), | ||
d.Get("host").(string)) | ||
|
||
if d.Get("grant").(bool) { | ||
stmtSQL = " WITH GRANT OPTION" | ||
} | ||
|
||
log.Println("Executing statement:", stmtSQL) | ||
_, _, err := conn.Query(stmtSQL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
user := fmt.Sprintf("%s@%s:%s", d.Get("user").(string), d.Get("host").(string), d.Get("database")) | ||
d.SetId(user) | ||
|
||
return ReadGrant(d, meta) | ||
} | ||
|
||
func ReadGrant(d *schema.ResourceData, meta interface{}) error { | ||
// At this time, all attributes are supplied by the user | ||
return nil | ||
} | ||
|
||
func DeleteGrant(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(mysqlc.Conn) | ||
|
||
stmtSQL := fmt.Sprintf("REVOKE GRANT OPTION ON %s.* FROM '%s'@'%s'", | ||
d.Get("database").(string), | ||
d.Get("user").(string), | ||
d.Get("host").(string)) | ||
|
||
log.Println("Executing statement:", stmtSQL) | ||
_, _, err := conn.Query(stmtSQL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
stmtSQL = fmt.Sprintf("REVOKE ALL ON %s.* FROM '%s'@'%s'", | ||
d.Get("database").(string), | ||
d.Get("user").(string), | ||
d.Get("host").(string)) | ||
|
||
log.Println("Executing statement:", stmtSQL) | ||
_, _, err = conn.Query(stmtSQL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,125 @@ | ||
package mysql | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"strings" | ||
"testing" | ||
|
||
mysqlc "github.com/ziutek/mymysql/mysql" | ||
|
||
"github.com/hashicorp/terraform/helper/resource" | ||
"github.com/hashicorp/terraform/terraform" | ||
) | ||
|
||
func TestAccGrant(t *testing.T) { | ||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccGrantCheckDestroy, | ||
Steps: []resource.TestStep{ | ||
resource.TestStep{ | ||
Config: testAccGrantConfig_basic, | ||
Check: resource.ComposeTestCheckFunc( | ||
testAccPrivilegeExists("mysql_grant.test", "SELECT"), | ||
resource.TestCheckResourceAttr("mysql_grant.test", "user", "jdoe"), | ||
resource.TestCheckResourceAttr("mysql_grant.test", "host", "example.com"), | ||
resource.TestCheckResourceAttr("mysql_grant.test", "database", "foo"), | ||
), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testAccPrivilegeExists(rn string, privilege string) resource.TestCheckFunc { | ||
return func(s *terraform.State) error { | ||
rs, ok := s.RootModule().Resources[rn] | ||
if !ok { | ||
return fmt.Errorf("resource not found: %s", rn) | ||
} | ||
|
||
if rs.Primary.ID == "" { | ||
return fmt.Errorf("grant id not set") | ||
} | ||
|
||
id := strings.Split(rs.Primary.ID, ":") | ||
userhost := strings.Split(id[0], "@") | ||
user := userhost[0] | ||
host := userhost[1] | ||
|
||
conn := testAccProvider.Meta().(mysqlc.Conn) | ||
stmtSQL := fmt.Sprintf("SHOW GRANTS for '%s'@'%s'", user, host) | ||
log.Println("Executing statement:", stmtSQL) | ||
rows, _, err := conn.Query(stmtSQL) | ||
if err != nil { | ||
return fmt.Errorf("error reading grant: %s", err) | ||
} | ||
|
||
if len(rows) == 0 { | ||
return fmt.Errorf("grant not found for '%s'@'%s'", user, host) | ||
} | ||
|
||
privilegeFound := false | ||
for _, row := range rows { | ||
log.Printf("Result Row: %s", row[0]) | ||
privIndex := strings.Index(string(row[0].([]byte)), privilege) | ||
if privIndex != -1 { | ||
privilegeFound = true | ||
} | ||
} | ||
|
||
if !privilegeFound { | ||
return fmt.Errorf("grant no found for '%s'@'%s'", user, host) | ||
} | ||
|
||
return nil | ||
} | ||
} | ||
|
||
func testAccGrantCheckDestroy(s *terraform.State) error { | ||
conn := testAccProvider.Meta().(mysqlc.Conn) | ||
|
||
for _, rs := range s.RootModule().Resources { | ||
if rs.Type != "mysql_grant" { | ||
continue | ||
} | ||
|
||
id := strings.Split(rs.Primary.ID, ":") | ||
userhost := strings.Split(id[0], "@") | ||
user := userhost[0] | ||
host := userhost[1] | ||
|
||
stmtSQL := fmt.Sprintf("SHOW GRANTS for '%s'@'%s'", user, host) | ||
log.Println("Executing statement:", stmtSQL) | ||
rows, _, err := conn.Query(stmtSQL) | ||
if err != nil { | ||
if mysqlErr, ok := err.(*mysqlc.Error); ok { | ||
if mysqlErr.Code == mysqlc.ER_NONEXISTING_GRANT { | ||
return nil | ||
} | ||
} | ||
|
||
return fmt.Errorf("error reading grant: %s", err) | ||
} | ||
|
||
if len(rows) != 0 { | ||
return fmt.Errorf("grant still exists for'%s'@'%s'", user, host) | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
const testAccGrantConfig_basic = ` | ||
resource "mysql_user" "test" { | ||
user = "jdoe" | ||
host = "example.com" | ||
password = "password" | ||
} | ||
resource "mysql_grant" "test" { | ||
user = "${mysql_user.test.user}" | ||
host = "${mysql_user.test.host}" | ||
database = "foo" | ||
privileges = ["UPDATE", "SELECT"] | ||
} | ||
` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package mysql | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
|
||
mysqlc "github.com/ziutek/mymysql/mysql" | ||
|
||
"github.com/hashicorp/terraform/helper/schema" | ||
) | ||
|
||
func resourceUser() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: CreateUser, | ||
Update: UpdateUser, | ||
Read: ReadUser, | ||
Delete: DeleteUser, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
"user": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
ForceNew: true, | ||
}, | ||
|
||
"host": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
ForceNew: true, | ||
Default: "localhost", | ||
}, | ||
|
||
"password": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, | ||
Sensitive: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func CreateUser(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(mysqlc.Conn) | ||
|
||
stmtSQL := fmt.Sprintf("CREATE USER '%s'@'%s'", | ||
d.Get("user").(string), | ||
d.Get("host").(string)) | ||
|
||
password := d.Get("password").(string) | ||
if password != "" { | ||
stmtSQL = stmtSQL + fmt.Sprintf(" IDENTIFIED BY '%s'", password) | ||
} | ||
|
||
log.Println("Executing statement:", stmtSQL) | ||
_, _, err := conn.Query(stmtSQL) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
user := fmt.Sprintf("%s@%s", d.Get("user").(string), d.Get("host").(string)) | ||
d.SetId(user) | ||
|
||
return nil | ||
} | ||
|
||
func UpdateUser(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(mysqlc.Conn) | ||
|
||
if d.HasChange("password") { | ||
_, newpw := d.GetChange("password") | ||
stmtSQL := fmt.Sprintf("ALTER USER '%s'@'%s' IDENTIFIED BY '%s'", | ||
d.Get("user").(string), | ||
d.Get("host").(string), | ||
newpw.(string)) | ||
|
||
log.Println("Executing query:", stmtSQL) | ||
_, _, err := conn.Query(stmtSQL) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func ReadUser(d *schema.ResourceData, meta interface{}) error { | ||
// At this time, all attributes are supplied by the user | ||
return nil | ||
} | ||
|
||
func DeleteUser(d *schema.ResourceData, meta interface{}) error { | ||
conn := meta.(mysqlc.Conn) | ||
|
||
stmtSQL := fmt.Sprintf("DROP USER '%s'@'%s'", | ||
d.Get("user").(string), | ||
d.Get("host").(string)) | ||
|
||
log.Println("Executing statement:", stmtSQL) | ||
|
||
_, _, err := conn.Query(stmtSQL) | ||
if err == nil { | ||
d.SetId("") | ||
} | ||
return err | ||
} |
Oops, something went wrong.