yunion.io/x/cloudmux@v0.3.10-0-alpha.1/pkg/multicloud/qcloud/rds_mysql_account.go (about)

     1  // Copyright 2019 Yunion
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package qcloud
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  
    21  	"gopkg.in/fatih/set.v0"
    22  
    23  	"yunion.io/x/pkg/errors"
    24  	"yunion.io/x/pkg/utils"
    25  
    26  	api "yunion.io/x/cloudmux/pkg/apis/compute"
    27  	"yunion.io/x/cloudmux/pkg/cloudprovider"
    28  	"yunion.io/x/cloudmux/pkg/multicloud"
    29  )
    30  
    31  type SMySQLInstanceAccount struct {
    32  	multicloud.SDBInstanceAccountBase
    33  	QcloudTags
    34  	rds *SMySQLInstance
    35  
    36  	Notes              string
    37  	Host               string
    38  	User               string
    39  	ModifyTime         string
    40  	ModifyPasswordTime string
    41  	CreateTime         string
    42  }
    43  
    44  func (self *SMySQLInstanceAccount) GetName() string {
    45  	return self.User
    46  }
    47  
    48  func (self *SMySQLInstanceAccount) GetHost() string {
    49  	return self.Host
    50  }
    51  
    52  func (self *SMySQLInstanceAccount) ResetPassword(password string) error {
    53  	return self.rds.region.ModifyMySQLAccountPassword(self.rds.InstanceId, password, map[string]string{self.User: self.Host})
    54  }
    55  
    56  func (self *SMySQLInstanceAccount) Delete() error {
    57  	return self.rds.region.DeleteMySQLAccounts(self.rds.InstanceId, map[string]string{self.User: self.Host})
    58  }
    59  
    60  type sPrivilege struct {
    61  	Database  string
    62  	Privilege string
    63  	User      string
    64  	Host      string
    65  }
    66  
    67  func (p sPrivilege) GetGlobalId() string {
    68  	return fmt.Sprintf("%s-%s-%s-%s", p.User, p.Host, p.Database, p.Privilege)
    69  }
    70  
    71  func (p sPrivilege) GetDBName() string {
    72  	return p.Database
    73  }
    74  
    75  func (p sPrivilege) GetPrivilege() string {
    76  	return p.Privilege
    77  }
    78  
    79  func (self *SRegion) GrantAccountPrivilege(instanceId, user, host, database, privilege string) error {
    80  	privileges := []string{}
    81  	switch privilege {
    82  	case api.DATABASE_PRIVILEGE_RW:
    83  		privileges = api.QCLOUD_RW_PRIVILEGE_SET
    84  	case api.DATABASE_PRIVILEGE_R:
    85  		privileges = api.QCLOUD_R_PRIVILEGE_SET
    86  	default:
    87  		return fmt.Errorf("unknow privilege %s", privilege)
    88  	}
    89  	priv, err := self.DescribeAccountPrivileges(instanceId, user, host)
    90  	if err != nil {
    91  		return errors.Wrapf(err, "DescribeAccountPrivileges")
    92  	}
    93  	params := map[string]string{
    94  		"InstanceId":      instanceId,
    95  		"Accounts.0.User": user,
    96  		"Accounts.0.Host": host,
    97  	}
    98  	for i, p := range priv.GlobalPrivileges {
    99  		params[fmt.Sprintf("GlobalPrivileges.%d", i)] = p
   100  	}
   101  	find := false
   102  	for i, p := range priv.DatabasePrivileges {
   103  		params[fmt.Sprintf("DatabasePrivileges.%d.Database", i)] = p.Database
   104  		if database == p.Database {
   105  			p.Privileges = privileges
   106  			find = true
   107  		}
   108  		for j, v := range p.Privileges {
   109  			params[fmt.Sprintf("DatabasePrivileges.%d.Privileges.%d", i, j)] = v
   110  		}
   111  	}
   112  	if !find {
   113  		params[fmt.Sprintf("DatabasePrivileges.%d.Database", len(priv.DatabasePrivileges))] = database
   114  		for j, v := range privileges {
   115  			params[fmt.Sprintf("DatabasePrivileges.%d.Privileges.%d", len(priv.DatabasePrivileges), j)] = v
   116  		}
   117  	}
   118  	for i, p := range priv.TablePrivileges {
   119  		params[fmt.Sprintf("TablePrivileges.%d.Database", i)] = p.Database
   120  		params[fmt.Sprintf("TablePrivileges.%d.Table", i)] = p.Table
   121  		for j, v := range p.Privileges {
   122  			params[fmt.Sprintf("TablePrivileges.%d.Privileges.%d", i, j)] = v
   123  		}
   124  	}
   125  	for i, p := range priv.ColumnPrivileges {
   126  		params[fmt.Sprintf("ColumnPrivileges.%d.Database", i)] = p.Database
   127  		params[fmt.Sprintf("ColumnPrivileges.%d.Table", i)] = p.Table
   128  		params[fmt.Sprintf("ColumnPrivileges.%d.Column", i)] = p.Column
   129  		for j, v := range p.Privileges {
   130  			params[fmt.Sprintf("ColumnPrivileges.%d.Privileges.%d", i, j)] = v
   131  		}
   132  	}
   133  	resp, err := self.cdbRequest("ModifyAccountPrivileges", params)
   134  	if err != nil {
   135  		return errors.Wrapf(err, "ModifyAccountPrivileges")
   136  	}
   137  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   138  	return self.waitAsyncAction("ModifyAccountPrivileges", instanceId, asyncRequestId)
   139  }
   140  
   141  func (self *SRegion) RevokeAccountPrivilege(instanceId, user, host, database string) error {
   142  	priv, err := self.DescribeAccountPrivileges(instanceId, user, host)
   143  	if err != nil {
   144  		return errors.Wrapf(err, "DescribeAccountPrivileges")
   145  	}
   146  	params := map[string]string{
   147  		"InstanceId":      instanceId,
   148  		"Accounts.0.User": user,
   149  		"Accounts.0.Host": host,
   150  	}
   151  	for i, p := range priv.GlobalPrivileges {
   152  		params[fmt.Sprintf("GlobalPrivileges.%d", i)] = p
   153  	}
   154  	idx := 0
   155  	for _, p := range priv.DatabasePrivileges {
   156  		if p.Database == database {
   157  			continue
   158  		}
   159  		params[fmt.Sprintf("DatabasePrivileges.%d.Database", idx)] = p.Database
   160  		for i, v := range p.Privileges {
   161  			params[fmt.Sprintf("DatabasePrivileges.%d.Privileges.%d", idx, i)] = v
   162  		}
   163  	}
   164  	for i, p := range priv.TablePrivileges {
   165  		params[fmt.Sprintf("TablePrivileges.%d.Database", i)] = p.Database
   166  		params[fmt.Sprintf("TablePrivileges.%d.Table", i)] = p.Table
   167  		for j, v := range p.Privileges {
   168  			params[fmt.Sprintf("TablePrivileges.%d.Privileges.%d", i, j)] = v
   169  		}
   170  	}
   171  	for i, p := range priv.ColumnPrivileges {
   172  		params[fmt.Sprintf("ColumnPrivileges.%d.Database", i)] = p.Database
   173  		params[fmt.Sprintf("ColumnPrivileges.%d.Table", i)] = p.Table
   174  		params[fmt.Sprintf("ColumnPrivileges.%d.Column", i)] = p.Column
   175  		for j, v := range p.Privileges {
   176  			params[fmt.Sprintf("ColumnPrivileges.%d.Privileges.%d", i, j)] = v
   177  		}
   178  	}
   179  	resp, err := self.cdbRequest("ModifyAccountPrivileges", params)
   180  	if err != nil {
   181  		return errors.Wrapf(err, "ModifyAccountPrivileges")
   182  	}
   183  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   184  	return self.waitAsyncAction("ModifyAccountPrivileges", instanceId, asyncRequestId)
   185  }
   186  
   187  func (self *SMySQLInstanceAccount) GrantPrivilege(database, privilege string) error {
   188  	return self.rds.region.GrantAccountPrivilege(self.rds.InstanceId, self.User, self.Host, database, privilege)
   189  }
   190  
   191  func (self *SMySQLInstanceAccount) RevokePrivilege(database string) error {
   192  	return self.rds.region.RevokeAccountPrivilege(self.rds.InstanceId, self.User, self.Host, database)
   193  }
   194  
   195  func (self *SMySQLInstanceAccount) GetIDBInstanceAccountPrivileges() ([]cloudprovider.ICloudDBInstanceAccountPrivilege, error) {
   196  	if utils.IsInStringArray(self.User, []string{"mysql.infoschema", "mysql.session", "mysql.sys"}) {
   197  		return []cloudprovider.ICloudDBInstanceAccountPrivilege{}, nil
   198  	}
   199  	priv, err := self.rds.region.DescribeAccountPrivileges(self.rds.InstanceId, self.User, self.Host)
   200  	if err != nil {
   201  		return nil, errors.Wrapf(err, "DescribeAccountPrivileges")
   202  	}
   203  	ret := []cloudprovider.ICloudDBInstanceAccountPrivilege{}
   204  	rwSet := set.New(set.ThreadSafe)
   205  	for _, p := range api.QCLOUD_RW_PRIVILEGE_SET {
   206  		rwSet.Add(p)
   207  	}
   208  	rSet := set.New(set.ThreadSafe)
   209  	for _, p := range api.QCLOUD_R_PRIVILEGE_SET {
   210  		rSet.Add(p)
   211  	}
   212  	for _, p := range priv.DatabasePrivileges {
   213  		pSet := set.New(set.ThreadSafe)
   214  		for _, v := range p.Privileges {
   215  			pSet.Add(v)
   216  		}
   217  		priv := strings.Join(p.Privileges, ",")
   218  		if pSet.IsEqual(rSet) {
   219  			priv = api.DATABASE_PRIVILEGE_R
   220  		} else if pSet.IsEqual(rwSet) {
   221  			priv = api.DATABASE_PRIVILEGE_RW
   222  		}
   223  		privilege := &sPrivilege{
   224  			Database:  p.Database,
   225  			User:      self.User,
   226  			Host:      self.Host,
   227  			Privilege: priv,
   228  		}
   229  		ret = append(ret, privilege)
   230  	}
   231  	return ret, nil
   232  }
   233  
   234  func (self *SRegion) ModifyMySQLAccountPassword(instanceId string, password string, users map[string]string) error {
   235  	params := map[string]string{
   236  		"InstanceId":  instanceId,
   237  		"NewPassword": password,
   238  	}
   239  	idx := 0
   240  	for user, host := range users {
   241  		params[fmt.Sprintf("Accounts.%d.user", idx)] = user
   242  		params[fmt.Sprintf("Accounts.%d.host", idx)] = host
   243  		idx++
   244  	}
   245  	resp, err := self.cdbRequest("ModifyAccountPassword", params)
   246  	if err != nil {
   247  		return errors.Wrapf(err, "ModifyAccountPassword")
   248  	}
   249  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   250  	return self.waitAsyncAction("ModifyAccountPassword", instanceId, asyncRequestId)
   251  }
   252  
   253  func (self *SRegion) DeleteMySQLAccounts(instanceId string, users map[string]string) error {
   254  	params := map[string]string{
   255  		"InstanceId": instanceId,
   256  	}
   257  	idx := 0
   258  	for user, host := range users {
   259  		params[fmt.Sprintf("Accounts.%d.user", idx)] = user
   260  		params[fmt.Sprintf("Accounts.%d.host", idx)] = host
   261  		idx++
   262  	}
   263  	resp, err := self.cdbRequest("DeleteAccounts", params)
   264  	if err != nil {
   265  		return errors.Wrapf(err, "DeleteAccounts")
   266  	}
   267  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   268  	return self.waitAsyncAction("DeleteAccounts", instanceId, asyncRequestId)
   269  }
   270  
   271  func (self *SRegion) DescribeMySQLAccounts(instanceId string, offset, limit int) ([]SMySQLInstanceAccount, int, error) {
   272  	if limit < 1 || limit > 100 {
   273  		limit = 100
   274  	}
   275  	params := map[string]string{
   276  		"InstanceId": instanceId,
   277  		"Offset":     fmt.Sprintf("%d", offset),
   278  		"Limit":      fmt.Sprintf("%d", limit),
   279  	}
   280  	resp, err := self.cdbRequest("DescribeAccounts", params)
   281  	if err != nil {
   282  		return nil, 0, errors.Wrapf(err, "DescribeAccounts")
   283  	}
   284  	ret := []SMySQLInstanceAccount{}
   285  	err = resp.Unmarshal(&ret, "Items")
   286  	if err != nil {
   287  		return nil, 0, errors.Wrapf(err, "resp.Unmarshal")
   288  	}
   289  	totalCount, _ := resp.Float("TotalCount")
   290  	return ret, int(totalCount), nil
   291  }
   292  
   293  func (self *SMySQLInstance) GetIDBInstanceAccounts() ([]cloudprovider.ICloudDBInstanceAccount, error) {
   294  	accounts := []SMySQLInstanceAccount{}
   295  	for {
   296  		part, total, err := self.region.DescribeMySQLAccounts(self.InstanceId, len(accounts), 100)
   297  		if err != nil {
   298  			return nil, errors.Wrapf(err, "DescribeMySQLAccounts")
   299  		}
   300  		accounts = append(accounts, part...)
   301  		if len(accounts) >= total {
   302  			break
   303  		}
   304  	}
   305  	ret := []cloudprovider.ICloudDBInstanceAccount{}
   306  	for i := range accounts {
   307  		if len(accounts[i].User) > 0 { // 忽略用户为空的用户
   308  			accounts[i].rds = self
   309  			ret = append(ret, &accounts[i])
   310  		}
   311  	}
   312  	return ret, nil
   313  }
   314  
   315  func (self *SRegion) CreateMySQLAccount(instanceId string, opts *cloudprovider.SDBInstanceAccountCreateConfig) error {
   316  	params := map[string]string{
   317  		"InstanceId":      instanceId,
   318  		"Password":        opts.Password,
   319  		"Accounts.0.User": opts.Name,
   320  		"Accounts.0.Host": opts.Host,
   321  		"Description":     opts.Description,
   322  	}
   323  	resp, err := self.cdbRequest("CreateAccounts", params)
   324  	if err != nil {
   325  		return errors.Wrapf(err, "CreateAccounts")
   326  	}
   327  	asyncRequestId, _ := resp.GetString("AsyncRequestId")
   328  	return self.waitAsyncAction("CreateAccounts", instanceId, asyncRequestId)
   329  }
   330  
   331  type SDatabasePrivilege struct {
   332  	Privileges []string
   333  	Database   string
   334  }
   335  
   336  type STablePrivilege struct {
   337  	Database   string
   338  	Table      string
   339  	Privileges []string
   340  }
   341  
   342  type SColumnPrivilege struct {
   343  	Database   string
   344  	Table      string
   345  	Column     string
   346  	Privileges []string
   347  }
   348  
   349  type SAccountPrivilege struct {
   350  	GlobalPrivileges   []string
   351  	DatabasePrivileges []SDatabasePrivilege
   352  	TablePrivileges    []STablePrivilege
   353  	ColumnPrivileges   []SColumnPrivilege
   354  }
   355  
   356  func (self *SRegion) DescribeAccountPrivileges(instanceId string, user, host string) (*SAccountPrivilege, error) {
   357  	params := map[string]string{
   358  		"InstanceId": instanceId,
   359  		"User":       user,
   360  		"Host":       host,
   361  	}
   362  	resp, err := self.cdbRequest("DescribeAccountPrivileges", params)
   363  	if err != nil {
   364  		return nil, errors.Wrapf(err, "DescribeAccountPrivileges")
   365  	}
   366  	priv := &SAccountPrivilege{}
   367  	err = resp.Unmarshal(priv)
   368  	if err != nil {
   369  		return nil, errors.Wrapf(err, "resp.Unmarshal")
   370  	}
   371  	return priv, nil
   372  }