diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/cmd/subcmd_dump.go b/dbm-services/mysql/db-tools/mysql-dbbackup/cmd/subcmd_dump.go index c3cf04c81a..20de195143 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/cmd/subcmd_dump.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/cmd/subcmd_dump.go @@ -195,13 +195,12 @@ func backupData(cnf *config.BackupConfig) (err error) { } logger.Log.Info("parse config file: end") if cnf.Public.DataSchemaGrant == cst.BackupNone { - logger.Log.Info("backup nothing, exit") + logger.Log.Infof("backup nothing for %d, exit", cnf.Public.MysqlPort) return nil } if err := precheck.BeforeDump(cnf); err != nil { return err } - // 备份权限 backup priv info // 注意:如果只备份权限,则走 backupexe.ExecuteBackup(cnf) 逻辑 // 如果还备份 schema/data,则走下面这个逻辑 diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/docs/loadbackup.md b/dbm-services/mysql/db-tools/mysql-dbbackup/docs/loadbackup.md index 3bdded1ea0..ffd4344f11 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/docs/loadbackup.md +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/docs/loadbackup.md @@ -45,6 +45,8 @@ Global Flags: 如果是 mydumper 备份出的文件,还可以通过 `--databases` 等选项控制导入的数据。mysqldump 导出的数据 使用 `--databases` 则会提示错误。 +逻辑备份导入 `loadbackup logical` 是不需要指定字符集,dbbackup 会自动使用导出后备份文件里面的 charset (即 .index 里面的 backup_charset ) + ### 物理备份 恢复: ``` ./dbbackup loadbackup physical --help diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper.go index df036bbdd3..afe95d0fe4 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper.go @@ -2,6 +2,7 @@ package backupexe import ( + "database/sql" "fmt" "strings" @@ -12,6 +13,7 @@ import ( "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/cst" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/dbareport" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/logger" + "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/util" ) @@ -24,7 +26,7 @@ type Dumper interface { } // BuildDumper return logical or physical dumper -func BuildDumper(cnf *config.BackupConfig, storageEngine string) (dumper Dumper, err error) { +func BuildDumper(cnf *config.BackupConfig, db *sql.DB) (dumper Dumper, err error) { if cnf.Public.IfBackupGrantOnly() { logger.Log.Infof("only backup grants for %d", cnf.Public.MysqlPort) cnf.Public.BackupType = cst.BackupLogical @@ -33,6 +35,10 @@ func BuildDumper(cnf *config.BackupConfig, storageEngine string) (dumper Dumper, } return dumper, nil } + storageEngine, err := mysqlconn.GetStorageEngine(db) + if err != nil { + return nil, err + } if err = precheck.CheckBackupType(cnf, storageEngine); err != nil { return nil, err } diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper_physical.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper_physical.go index 60b42c6ce7..b8fc460ff9 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper_physical.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/dumper_physical.go @@ -209,7 +209,7 @@ func (p *PhysicalDumper) Execute(enableTimeOut bool) error { err = cmd.Run() if err != nil { - logger.Log.Error("run physical backup failed: ", err, stderr.Bytes()) + logger.Log.Error("run physical backup failed: ", err, stderr.String()) return errors.WithMessage(err, stderr.String()) } return nil @@ -259,15 +259,15 @@ func (p *PhysicalDumper) PrepareBackupMetaInfo(cnf *config.BackupConfig) (*dbare // parse xtrabackup_info if err = parseXtraInfo(qpressPath, xtrabackupInfoFileName, tmpFileName, &metaInfo); err != nil { logger.Log.Warnf("xtrabackup_info file not found, use current time as BackupEndTime, err: %s", err.Error()) - metaInfo.BackupBeginTime, _ = time.Parse(time.DateTime, p.backupStartTime.Format(time.DateTime)) - metaInfo.BackupEndTime, _ = time.Parse(time.DateTime, p.backupEndTime.Format(time.DateTime)) + metaInfo.BackupBeginTime = cmutil.TimeToSecondPrecision(p.backupStartTime) + metaInfo.BackupEndTime = cmutil.TimeToSecondPrecision(p.backupEndTime) } // parse xtrabackup_timestamp_info if err := parseXtraTimestamp(qpressPath, xtrabackupTimestampFileName, tmpFileName, &metaInfo); err != nil { // 此时刚备份完成,还没有开始打包,这里把当前时间认为是 consistent_time,不完善! logger.Log.Warnf("xtrabackup_timestamp_info file not found, "+ "use current time as Consistent Time, err: %s", err.Error()) - metaInfo.BackupConsistentTime, _ = time.Parse(time.DateTime, p.backupEndTime.Format(time.DateTime)) + metaInfo.BackupConsistentTime = cmutil.TimeToSecondPrecision(p.backupEndTime) } // parse xtrabackup_binlog_info 本机的 binlog file,pos if masterStatus, err := parseXtraBinlogInfo(qpressPath, xtrabackupBinlogInfoFileName, tmpFileName); err != nil { diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/execute_dump.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/execute_dump.go index b48ff88ab9..55d90ca7b7 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/execute_dump.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/backupexe/execute_dump.go @@ -34,16 +34,10 @@ func ExecuteBackup(cnf *config.BackupConfig) (*dbareport.IndexContent, error) { return nil, err } - storageEngine, err := mysqlconn.GetStorageEngine(db) - if err != nil { - return nil, err - } - storageEngine = strings.ToLower(storageEngine) - mysqlVersion, isOfficial := util.VersionParser(versionStr) XbcryptBin = GetXbcryptBin(mysqlVersion, isOfficial) - dumper, err := BuildDumper(cnf, storageEngine) // 会在里面确定备份方式 + dumper, err := BuildDumper(cnf, db) // 会在里面确定备份方式 if err != nil { return nil, err } diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn/conn.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn/conn.go index 11bc1f780c..091bd94a29 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn/conn.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn/conn.go @@ -122,14 +122,15 @@ func GetBinlogFormat(dbh *sql.DB) (string, string) { return binlogFormat, binlogRowImage } -// GetStorageEngine Get the storage engine from mysql server +// GetStorageEngine Get the storage engine from mysql server, to lower func GetStorageEngine(dbh *sql.DB) (string, error) { version, err := MysqlSingleColumnQuery("select @@default_storage_engine", dbh) if err != nil { logger.Log.Error("can't get default storage_engine, error :", err) return "", err } - return version[0], nil + + return strings.ToLower(version[0]), nil } // GetDataDir get datadir from mysql server diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_backup_type.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_backup_type.go new file mode 100644 index 0000000000..7f15f53541 --- /dev/null +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_backup_type.go @@ -0,0 +1,39 @@ +package precheck + +import ( + "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/config" + "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/cst" + "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/logger" + "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/util" +) + +// CheckBackupType check and fix backup type +func CheckBackupType(cnf *config.BackupConfig, storageEngine string) error { + backupSize, err := util.CalServerDataSize(cnf.Public.MysqlPort) + if err != nil { + return err + } + if cnf.Public.BackupType == cst.BackupTypeAuto { + if storageEngine == cst.StorageEngineTokudb || storageEngine == cst.StorageEngineRocksdb { + logger.Log.Infof("BackupType auto with engine=%s, use physical", storageEngine) + cnf.Public.BackupType = cst.BackupPhysical + return nil + } + // report 时需要用真实的 backup type + if backupSize > cst.BackupTypeAutoDataSizeGB*1024*1024*1024 { + logger.Log.Infof("data size %d for port %d is larger than %d GB, use physical", + backupSize, cnf.Public.MysqlPort, cst.BackupTypeAutoDataSizeGB) + cnf.Public.BackupType = cst.BackupPhysical + } else { + cnf.Public.BackupType = cst.BackupLogical + } + if glibcVer, err := util.GetGlibcVersion(); err != nil { + logger.Log.Warn("failed to glibc version, err:", err) + } else if glibcVer < "2.14" { + // mydumper need glibc version >= 2.14 + logger.Log.Infof("BackupType auto with glibc version %s < 2.14, use physical", glibcVer) + cnf.Public.BackupType = cst.BackupPhysical + } + } + return nil +} diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_charset.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_charset.go index ac73118a54..a7aeb59e08 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_charset.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_charset.go @@ -7,19 +7,18 @@ import ( "github.com/pkg/errors" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/config" - "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/cst" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/common" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/logger" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/util" ) -// CheckCharset Check Mysql server charset -func CheckCharset(cnf *config.Public, dbh *sql.DB) error { - if strings.ToLower(cnf.BackupType) != cst.BackupLogical { - return nil - } - confCharset := cnf.MysqlCharset +// CheckCharset Check and fix mysql server charset +func CheckCharset(cnf *config.BackupConfig, dbh *sql.DB) error { + //if strings.ToLower(cnf.Public.BackupType) != cst.BackupLogical { + // return nil + //} + confCharset := cnf.Public.MysqlCharset var superCharset string version, verErr := mysqlconn.GetMysqlVersion(dbh) @@ -32,17 +31,18 @@ func CheckCharset(cnf *config.Public, dbh *sql.DB) error { } else { superCharset = "utf8mb4" } + if confCharset == "auto" || confCharset == "" { // 如果 cnf.MysqlCharset 为空,则自动读取 character_set_server serverCharset, err := mysqlconn.MysqlSingleColumnQuery("select @@character_set_server", dbh) if err != nil { logger.Log.Error("can't select mysql server charset , error :", err) - return errors.WithMessagef(err, "failed to get character_set_server from %d", cnf.MysqlPort) + return errors.WithMessagef(err, "failed to get character_set_server from %d", cnf.Public.MysqlPort) } - cnf.MysqlCharset = serverCharset[0] + cnf.Public.MysqlCharset = serverCharset[0] return nil } - if confCharset != "binary" && confCharset != superCharset && strings.ToUpper(cnf.DataSchemaGrant) == "ALL" { + if confCharset != "binary" && confCharset != superCharset && strings.ToUpper(cnf.Public.DataSchemaGrant) == "ALL" { var goodCharset = []string{"latin1", "utf8", "utf8mb4"} serverCharset, err := mysqlconn.GetMysqlCharset(dbh) @@ -60,26 +60,26 @@ func CheckCharset(cnf *config.Public, dbh *sql.DB) error { if err != nil { logger.Log.Warn("get_server_data_charsets query failed,use super charset") - cnf.MysqlCharset = superCharset + cnf.Public.MysqlCharset = superCharset } else if len(serverCharset) > 1 { logger.Log.Warn("found multi character sets on server ") - cnf.MysqlCharset = superCharset + cnf.Public.MysqlCharset = superCharset } else if len(serverCharset) == 1 { - cnf.MysqlCharset = serverCharset[0] + cnf.Public.MysqlCharset = serverCharset[0] if serverCharset[0] != confCharset { logger.Log.Warn("backup config charset:'%s' and server charset '%s' are not the same."+ " You should use %s to backup,please modify config charset to remove this warning", confCharset, serverCharset[0], serverCharset[0]) } } else { - tableNum := common.GetTableNum(cnf.MysqlPort) // todo + tableNum := common.GetTableNum(cnf.Public.MysqlPort) // todo if tableNum > 1000 { - cnf.MysqlCharset = superCharset + cnf.Public.MysqlCharset = superCharset logger.Log.Warn("too much table, tableNum is %d,check server charset failed,"+ "use super charset:%s to backup.", tableNum, superCharset) } } } - logger.Log.Info("use character set:", cnf.MysqlCharset, " to backup") + logger.Log.Info("use character set:", cnf.Public.MysqlCharset, " to backup") return nil } diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_space.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_disk_space.go similarity index 100% rename from dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_space.go rename to dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/check_disk_space.go diff --git a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/precheck.go b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/precheck.go index cdfc3e3625..d74b57759d 100644 --- a/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/precheck.go +++ b/dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/precheck/precheck.go @@ -10,7 +10,6 @@ import ( "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/cst" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/logger" "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/src/mysqlconn" - "dbm-services/mysql/db-tools/mysql-dbbackup/pkg/util" ) // BeforeDump precheck before dumping backup @@ -27,11 +26,13 @@ func BeforeDump(cnf *config.BackupConfig) error { defer func() { _ = dbh.Close() }() - /* - if err := CheckBackupType(cnf); err != nil { - return err - } - */ + storageEngine, err := mysqlconn.GetStorageEngine(dbh) + if err != nil { + return err + } + if err := CheckBackupType(cnf, storageEngine); err != nil { + return err + } cnfPublic := &cnf.Public /* @@ -40,8 +41,8 @@ func BeforeDump(cnf *config.BackupConfig) error { return err } */ - // check server charset - if err := CheckCharset(cnfPublic, dbh); err != nil { + // check server charset, need correct charset + if err := CheckCharset(cnf, dbh); err != nil { logger.Log.Errorf("failed to get Mysqlcharset for %d", cnfPublic.MysqlPort) return err } @@ -62,37 +63,6 @@ func BeforeDump(cnf *config.BackupConfig) error { return nil } -// CheckBackupType check and fix backup type -func CheckBackupType(cnf *config.BackupConfig, engine string) error { - backupSize, err := util.CalServerDataSize(cnf.Public.MysqlPort) - if err != nil { - return err - } - if cnf.Public.BackupType == cst.BackupTypeAuto { - if engine == cst.StorageEngineTokudb || engine == cst.StorageEngineRocksdb { - logger.Log.Infof("BackupType auto with engine=%s, use physical", engine) - cnf.Public.BackupType = cst.BackupPhysical - return nil - } - // report 时需要用真实的 backup type - if backupSize > cst.BackupTypeAutoDataSizeGB*1024*1024*1024 { - logger.Log.Infof("data size %d for port %d is larger than %d GB, use physical", - backupSize, cnf.Public.MysqlPort, cst.BackupTypeAutoDataSizeGB) - cnf.Public.BackupType = cst.BackupPhysical - } else { - cnf.Public.BackupType = cst.BackupLogical - } - if glibcVer, err := util.GetGlibcVersion(); err != nil { - logger.Log.Warn("failed to glibc version, err:", err) - } else if glibcVer < "2.14" { - // mydumper need glibc version >= 2.14 - logger.Log.Infof("BackupType auto with glibc version %s < 2.14, use physical", glibcVer) - cnf.Public.BackupType = cst.BackupPhysical - } - } - return nil -} - // CheckEngineTables 只有在 master 上进行物理备份数据时,才执行检查 func CheckEngineTables(cnf *config.BackupConfig, db *sql.DB) error { if !(cnf.Public.BackupType == cst.BackupPhysical && diff --git a/dbm-ui/backend/configuration/constants.py b/dbm-ui/backend/configuration/constants.py index 34e4424426..30c5bedcf2 100644 --- a/dbm-ui/backend/configuration/constants.py +++ b/dbm-ui/backend/configuration/constants.py @@ -99,6 +99,7 @@ class SystemSettingsEnum(str, StructuredEnum): BKM_DBM_TOKEN = EnumField("BKM_DBM_TOKEN", _("监控数据源token")) BKM_DBM_REPORT = EnumField("BKM_DBM_REPORT", _("mysql/redis-监控自定义上报: dataid/token")) FREE_BK_MODULE_ID = EnumField("FREE_BK_MODULE_ID", _("业务空闲模块ID")) + VIRTUAL_USERS = EnumField("VIRTUAL_USERS", _("平台调用的虚拟账号列表")) # 主机默认统一转移到 DBM 业务下托管,若业务 ID 属于这个列表,则转移到对应的业务下 INDEPENDENT_HOSTING_BIZS = EnumField("INDEPENDENT_HOSTING_BIZS", _("独立托管机器的业务列表")) BF_WHITELIST_BIZS = EnumField("BF_WHITELIST_BIZS", _("BF业务白名单")) @@ -213,6 +214,7 @@ class BizSettingsEnum(str, StructuredEnum): [SystemSettingsEnum.AFFINITY, "list", [], _("环境的容灾要求")], [SystemSettingsEnum.SYSTEM_MSG_TYPE, "list", ["weixin", "mail"], _("系统消息通知方式")], [SystemSettingsEnum.PADDING_PROXY_CLUSTER_LIST, "list", [], _("补全proxy的集群域名列表")], + [SystemSettingsEnum.VIRTUAL_USERS, "list", [], _("平台调用的虚拟账户列表")], ] # 环境配置项 是否支持DNS解析 pulsar flow used diff --git a/dbm-ui/backend/db_services/dbpermission/constants.py b/dbm-ui/backend/db_services/dbpermission/constants.py index 35e7da862f..423483d7fe 100644 --- a/dbm-ui/backend/db_services/dbpermission/constants.py +++ b/dbm-ui/backend/db_services/dbpermission/constants.py @@ -107,6 +107,15 @@ class FormatType(str, StructuredEnum): CLUSTER = EnumField("cluster", _("cluster")) +class RuleActionType(str, StructuredEnum): + """权限操作类型""" + + AUTH = EnumField("auth", _("授权")) + CREATE = EnumField("create", _("创建")) + CHANGE = EnumField("change", _("修改")) + DELETE = EnumField("delete", _("删除")) + + class AuthorizeExcelHeader(str, StructuredEnum): """授权excel的头部信息""" @@ -117,6 +126,14 @@ class AuthorizeExcelHeader(str, StructuredEnum): ERROR = EnumField("错误信息/提示信息", _("错误信息/提示信息")) +# 授权字段和授权excel头的映射 +AUTHORIZE_KEY__EXCEL_FIELD_MAP = { + "user": AuthorizeExcelHeader.USER, + "access_dbs": AuthorizeExcelHeader.ACCESS_DBS, + "source_ips": AuthorizeExcelHeader.SOURCE_IPS, + "target_instances": AuthorizeExcelHeader.TARGET_INSTANCES, +} + # 授权数据过期时间 AUTHORIZE_DATA_EXPIRE_TIME = 60 * 60 * 6 @@ -126,5 +143,4 @@ class AuthorizeExcelHeader(str, StructuredEnum): # 账号名称最大长度 MAX_ACCOUNT_LENGTH = 31 - DPRIV_PARAMETER_MAP = {"account_type": "cluster_type", "rule_ids": "ids", "privilege": "privs", "access_db": "dbname"} diff --git a/dbm-ui/backend/db_services/dbpermission/db_account/handlers.py b/dbm-ui/backend/db_services/dbpermission/db_account/handlers.py index 8ecab64a84..7a78ef65b9 100644 --- a/dbm-ui/backend/db_services/dbpermission/db_account/handlers.py +++ b/dbm-ui/backend/db_services/dbpermission/db_account/handlers.py @@ -19,7 +19,7 @@ from backend.components.mysql_priv_manager.client import DBPrivManagerApi from backend.core.encrypt.constants import AsymmetricCipherConfigType from backend.core.encrypt.handlers import AsymmetricHandler -from backend.db_services.dbpermission.constants import DPRIV_PARAMETER_MAP, AccountType +from backend.db_services.dbpermission.constants import DPRIV_PARAMETER_MAP, AccountType, RuleActionType from backend.db_services.dbpermission.db_account.dataclass import ( AccountMeta, AccountPrivMeta, @@ -27,8 +27,11 @@ AccountUserMeta, ) from backend.db_services.dbpermission.db_account.signals import create_account_signal +from backend.db_services.dbpermission.db_authorize.models import DBRuleActionLog from backend.db_services.mysql.open_area.models import TendbOpenAreaConfig from backend.db_services.mysql.permission.exceptions import DBPermissionBaseException +from backend.ticket.constants import TicketStatus, TicketType +from backend.ticket.models import Ticket from backend.utils.excel import ExcelHandler logger = logging.getLogger("root") @@ -63,6 +66,17 @@ def _decrypt_password(password: str) -> str: def _format_account_rules(self, account_rules_list: Dict) -> Dict: """格式化账号权限列表信息""" + # 查询规则变更的单据,补充规则正在运行的状态 + try: + ticket_type = getattr(TicketType, f"{self.account_type.upper()}_ACCOUNT_RULE_CHANGE") + priv_tickets = Ticket.objects.filter(status=TicketStatus.RUNNING, ticket_type=ticket_type) + rule__action_map = {} + for t in priv_tickets: + rule__action_map[t.details["rule_id"]] = {"action": t.details["action"], "ticket_id": t.id} + except (AttributeError, Exception): + rule__action_map = {} + + # 格式化账号规则的字段信息 for account_rules in account_rules_list["items"]: account_rules["account"]["account_id"] = account_rules["account"].pop("id") @@ -74,6 +88,7 @@ def _format_account_rules(self, account_rules_list: Dict) -> Dict: rule["rule_id"] = rule.pop("id") rule["access_db"] = rule.pop("dbname") rule["privilege"] = rule.pop("priv") + rule["priv_ticket"] = rule__action_map.get(rule["rule_id"], {}) return account_rules_list @@ -147,6 +162,7 @@ def add_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]: "dbname": account_rule.access_db, } ) + # DBRuleActionLog.create_log(account_rule, self.operator, action=RuleActionType.CHANGE) return resp def query_account_rules(self, account_rule: AccountRuleMeta): @@ -211,7 +227,6 @@ def modify_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]: - 修改账号规则 :param account_rule: 账号规则元信息 """ - resp = DBPrivManagerApi.modify_account_rule( { "bk_biz_id": self.bk_biz_id, @@ -223,6 +238,9 @@ def modify_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]: "priv": account_rule.privilege, } ) + DBRuleActionLog.create_log( + account_rule.account_id, account_rule.rule_id, self.operator, action=RuleActionType.CHANGE + ) return resp def delete_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]: @@ -233,7 +251,7 @@ def delete_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]: # 如果账号规则与其他地方耦合,需要进行判断 config = TendbOpenAreaConfig.objects.filter(related_authorize__contains=[account_rule.rule_id]) if config.exists(): - raise DBPermissionBaseException(_("当前授权规则已被开区模板{}引用,不允许删除").format(config.first().name)) + raise DBPermissionBaseException(_("当前规则已被开区模板{}引用,不允许删除").format(config.first().config_name)) resp = DBPrivManagerApi.delete_account_rule( { @@ -243,18 +261,22 @@ def delete_account_rule(self, account_rule: AccountRuleMeta) -> Optional[Any]: "id": [account_rule.rule_id], } ) + DBRuleActionLog.create_log( + account_rule.account_id, account_rule.rule_id, self.operator, action=RuleActionType.DELETE + ) return resp @classmethod - def aggregate_user_db_privileges(cls, bk_biz_id: int, account_type: AccountType) -> Dict[str, Dict[str, List]]: - account_rules = DBPrivManagerApi.list_account_rules({"bk_biz_id": bk_biz_id, "cluster_type": account_type})[ - "items" - ] - # 按照user,accessdb进行聚合 + def aggregate_user_db_rules( + cls, bk_biz_id: int, account_type: AccountType, rule_key: str = "priv" + ) -> Dict[str, Dict[str, Any]]: + """获得user和db对应的规则对象""" + account_rules = DBPrivManagerApi.list_account_rules({"bk_biz_id": bk_biz_id, "cluster_type": account_type}) + # 按照user,accessdb进行聚合规则 user_db__rules = defaultdict(dict) - for account_rule in account_rules: + for account_rule in account_rules["items"]: account, rules = account_rule["account"], account_rule["rules"] - user_db__rules[account["user"]] = {rule["dbname"]: rule["priv"] for rule in rules} + user_db__rules[account["user"]] = {rule["dbname"]: rule[rule_key] if rule_key else rule for rule in rules} return user_db__rules def paginate_match_ips(self, data, privs_format, offset, limit): diff --git a/dbm-ui/backend/db_services/dbpermission/db_account/serializers.py b/dbm-ui/backend/db_services/dbpermission/db_account/serializers.py index 543081018d..eaa1773599 100644 --- a/dbm-ui/backend/db_services/dbpermission/db_account/serializers.py +++ b/dbm-ui/backend/db_services/dbpermission/db_account/serializers.py @@ -190,24 +190,21 @@ class RuleTypeSerializer(serializers.Serializer): ) account_id = serializers.IntegerField(help_text=_("账号ID")) - access_db = serializers.CharField(help_text=_("访问DB")) - privilege = RuleTypeSerializer(help_text=_("授权规则")) + access_db = serializers.CharField(help_text=_("访问DB"), required=False) + privilege = RuleTypeSerializer(help_text=_("授权规则"), required=False) account_type = serializers.ChoiceField(help_text=_("账号类型"), choices=AccountType.get_choices()) class Meta: swagger_schema_fields = {"example": mock_data.ADD_MYSQL_ACCOUNT_RULE_REQUEST} -class ModifyMySQLAccountRuleSerializer(AddAccountRuleSerializer): +class ModifyAccountRuleSerializer(AddAccountRuleSerializer): rule_id = serializers.IntegerField(help_text=_("规则ID")) class Meta: swagger_schema_fields = {"example": mock_data.MODIFY_MYSQL_ACCOUNT_RULE_REQUEST} -class DeleteAccountRuleSerializer(serializers.Serializer): - rule_id = serializers.IntegerField(help_text=_("规则ID")) - account_type = serializers.ChoiceField(help_text=_("账号类型"), choices=AccountType.get_choices()) - +class DeleteAccountRuleSerializer(ModifyAccountRuleSerializer): class Meta: swagger_schema_fields = {"example": mock_data.DELETE_MYSQL_ACCOUNT_RULE_REQUEST} diff --git a/dbm-ui/backend/db_services/dbpermission/db_account/views.py b/dbm-ui/backend/db_services/dbpermission/db_account/views.py index 3911cb569a..956b08195a 100644 --- a/dbm-ui/backend/db_services/dbpermission/db_account/views.py +++ b/dbm-ui/backend/db_services/dbpermission/db_account/views.py @@ -34,7 +34,7 @@ FilterAccountRulesSerializer, FilterPrivSerializer, ListAccountRulesSerializer, - ModifyMySQLAccountRuleSerializer, + ModifyAccountRuleSerializer, PageAccountRulesSerializer, QueryAccountRulesSerializer, UpdateAccountPasswordSerializer, @@ -177,9 +177,9 @@ def query_account_rules(self, request, bk_biz_id): ) @common_swagger_auto_schema( - operation_summary=_("修改账号规则"), request_body=ModifyMySQLAccountRuleSerializer(), tags=[SWAGGER_TAG] + operation_summary=_("修改账号规则"), request_body=ModifyAccountRuleSerializer(), tags=[SWAGGER_TAG] ) - @action(methods=["POST"], detail=False, serializer_class=ModifyMySQLAccountRuleSerializer) + @action(methods=["POST"], detail=False, serializer_class=ModifyAccountRuleSerializer) def modify_account_rule(self, request, bk_biz_id): return self._view_common_handler( request=request, diff --git a/dbm-ui/backend/db_services/dbpermission/db_authorize/dataclass.py b/dbm-ui/backend/db_services/dbpermission/db_authorize/dataclass.py index d660215e6c..400a5e919b 100644 --- a/dbm-ui/backend/db_services/dbpermission/db_authorize/dataclass.py +++ b/dbm-ui/backend/db_services/dbpermission/db_authorize/dataclass.py @@ -18,7 +18,14 @@ from backend.constants import IP_RE_PATTERN from backend.db_meta.enums import ClusterEntryType from backend.db_meta.models import ClusterEntry -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord +from backend.db_services.dbpermission.constants import ( + AUTHORIZE_KEY__EXCEL_FIELD_MAP, + EXCEL_DIVIDER, + AuthorizeExcelHeader, +) +from backend.flow.plugins.components.collections.common.base_service import BaseService +from backend.ticket.constants import FlowType +from backend.ticket.models import Flow @dataclass @@ -48,12 +55,35 @@ def from_dict(cls, init_data: Dict) -> "AuthorizeMeta": @classmethod def from_excel_data(cls, excel_data: Dict, cluster_type: str) -> "AuthorizeMeta": """从权限excel数据解析为AuthorizeMeta, 每个集群类型自己实现""" - raise NotImplementedError + return cls( + user=excel_data[AuthorizeExcelHeader.USER], + access_dbs=excel_data[AuthorizeExcelHeader.ACCESS_DBS].split(EXCEL_DIVIDER), + target_instances=excel_data[AuthorizeExcelHeader.TARGET_INSTANCES].split(EXCEL_DIVIDER), + source_ips=ExcelAuthorizeMeta.format_ip(excel_data.get(AuthorizeExcelHeader.SOURCE_IPS)), + cluster_type=cluster_type, + ) @classmethod - def serializer_record_data(cls, record_queryset: List[AuthorizeRecord]) -> List[Dict]: + def serializer_record_data(cls, ticket_id: int) -> List[Dict]: """将授权记录进行序列化""" - raise NotImplementedError + + def __format(_data): + if isinstance(_data, (list, dict)): + return "\n".join(_data) + return _data + + # 目前授权只有:授权单据和开区单据,仅一个inner flow,可以直接取first + flow = Flow.objects.filter(ticket_id=ticket_id, flow_type=FlowType.INNER_FLOW).first() + authorize_results = BaseService.get_flow_output(flow)["data"].get("authorize_results", []) + field_map = AUTHORIZE_KEY__EXCEL_FIELD_MAP + + record_data_list = [] + for index, info in enumerate(flow.details["ticket_data"]["rules_set"]): + data = {field_map[field]: __format(value) for field, value in info.items() if field in field_map} + data.update({AuthorizeExcelHeader.ERROR: authorize_results[index]}) + record_data_list.append(data) + + return record_data_list @dataclass @@ -73,6 +103,8 @@ def from_dict(cls, init_data: Dict) -> "ExcelAuthorizeMeta": @classmethod def format_ip(cls, ips: str): + if not ips: + return [] # 编译捕获ip:port的正则表达式(注意用?:取消分组) ip_pattern = re.compile(IP_RE_PATTERN) return ip_pattern.findall(ips) @@ -80,4 +112,11 @@ def format_ip(cls, ips: str): @classmethod def serialize_excel_data(cls, data: Dict) -> Dict: """将数据解析为权限excel data类的数据, 每个集群类型自己实现""" - raise NotImplementedError + excel_data = { + AuthorizeExcelHeader.USER: data["user"], + AuthorizeExcelHeader.TARGET_INSTANCES: EXCEL_DIVIDER.join(data["target_instances"]), + AuthorizeExcelHeader.ACCESS_DBS: EXCEL_DIVIDER.join([rule["dbname"] for rule in data["account_rules"]]), + AuthorizeExcelHeader.SOURCE_IPS: EXCEL_DIVIDER.join(cls.format_ip(str(data.get("source_ips")))), + AuthorizeExcelHeader.ERROR: data["message"], + } + return excel_data diff --git a/dbm-ui/backend/db_services/dbpermission/db_authorize/handlers.py b/dbm-ui/backend/db_services/dbpermission/db_authorize/handlers.py index 42fd96b281..05243a5bff 100644 --- a/dbm-ui/backend/db_services/dbpermission/db_authorize/handlers.py +++ b/dbm-ui/backend/db_services/dbpermission/db_authorize/handlers.py @@ -22,7 +22,6 @@ from backend.db_meta.enums import ClusterType from backend.db_services.dbpermission.constants import AUTHORIZE_DATA_EXPIRE_TIME, AccountType from backend.db_services.dbpermission.db_authorize.dataclass import AuthorizeMeta, ExcelAuthorizeMeta -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord from backend.utils.cache import data_cache from backend.utils.excel import ExcelHandler @@ -195,8 +194,7 @@ def get_authorize_info_excel(self, excel_authorize: ExcelAuthorizeMeta) -> HttpR # 单据走来的excel下载 if excel_authorize.ticket_id: excel_name = "authorize_results.xlsx" - record_queryset = AuthorizeRecord.get_authorize_records_by_ticket(excel_authorize.ticket_id) - excel_data_dict__list = self.authorize_meta.serializer_record_data(record_queryset) + excel_data_dict__list = self.authorize_meta.serializer_record_data(excel_authorize.ticket_id) # 授权ID走来的excel下载 if excel_authorize.authorize_uid: diff --git a/dbm-ui/backend/db_services/dbpermission/db_authorize/migrations/0002_auto_20240902_1106.py b/dbm-ui/backend/db_services/dbpermission/db_authorize/migrations/0002_auto_20240902_1106.py new file mode 100644 index 0000000000..02974803e2 --- /dev/null +++ b/dbm-ui/backend/db_services/dbpermission/db_authorize/migrations/0002_auto_20240902_1106.py @@ -0,0 +1,44 @@ +# Generated by Django 3.2.25 on 2024-09-02 03:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("db_authorize", "0001_initial"), + ] + + operations = [ + migrations.CreateModel( + name="DBRuleActionLog", + fields=[ + ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")), + ("operator", models.CharField(max_length=32, verbose_name="操作人")), + ("record_time", models.DateTimeField(auto_now=True, verbose_name="记录时间")), + ("account_id", models.IntegerField(verbose_name="账号ID")), + ("rule_id", models.IntegerField(verbose_name="权限规则ID")), + ( + "action_type", + models.CharField( + choices=[("auth", "授权"), ("create", "创建"), ("change", "修改"), ("delete", "删除")], + max_length=32, + verbose_name="操作类型", + ), + ), + ], + options={ + "verbose_name": "权限变更记录", + "verbose_name_plural": "权限变更记录", + "ordering": ["-record_time"], + }, + ), + migrations.AddIndex( + model_name="dbruleactionlog", + index=models.Index(fields=["rule_id"], name="db_authoriz_rule_id_35a12d_idx"), + ), + migrations.AddIndex( + model_name="dbruleactionlog", + index=models.Index(fields=["account_id"], name="db_authoriz_account_06c480_idx"), + ), + ] diff --git a/dbm-ui/backend/db_services/dbpermission/db_authorize/models.py b/dbm-ui/backend/db_services/dbpermission/db_authorize/models.py index 4df52e223d..1cd2664ac6 100644 --- a/dbm-ui/backend/db_services/dbpermission/db_authorize/models.py +++ b/dbm-ui/backend/db_services/dbpermission/db_authorize/models.py @@ -12,12 +12,17 @@ from django.db import models from django.utils.translation import ugettext_lazy as _ +from backend.bk_web.constants import LEN_SHORT +from backend.components import UserManagerApi +from backend.configuration.constants import SystemSettingsEnum +from backend.configuration.models import SystemSettings +from backend.db_services.dbpermission.constants import RuleActionType from backend.ticket.models import Ticket class AuthorizeRecord(models.Model): """ - 授权记录 + 授权记录 TODO: 请不要使用此表,以后会删除 """ ticket = models.ForeignKey(Ticket, help_text=_("关联工单"), related_name="authorize_records", on_delete=models.CASCADE) @@ -39,3 +44,41 @@ class Meta: @classmethod def get_authorize_records_by_ticket(cls, ticket_id: int): return cls.objects.filter(ticket__pk=ticket_id) + + +class DBRuleActionLog(models.Model): + """ + 权限变更记录:包括授权、修改、删除、创建 + """ + + operator = models.CharField(_("操作人"), max_length=LEN_SHORT) + record_time = models.DateTimeField(_("记录时间"), auto_now=True) + account_id = models.IntegerField(_("账号ID")) + rule_id = models.IntegerField(_("权限规则ID")) + action_type = models.CharField(_("操作类型"), choices=RuleActionType.get_choices(), max_length=LEN_SHORT) + + class Meta: + verbose_name = _("权限变更记录") + verbose_name_plural = _("权限变更记录") + ordering = ["-record_time"] + indexes = [ + models.Index(fields=["rule_id"]), + models.Index(fields=["account_id"]), + ] + + @classmethod + def create_log(cls, account_id, rule_id, operator, action): + log = DBRuleActionLog.objects.create( + rule_id=rule_id, account_id=account_id, operator=operator, action_type=action + ) + return log + + @classmethod + def get_notifiers(cls, rule_id): + """获取通知人列表,目前只过滤有授权记录的人""" + users = cls.objects.filter(rule_id=rule_id, action_type=RuleActionType.AUTH).values_list("operator", flat=True) + # 过滤离职的和虚拟账户 + virtual_users = SystemSettings.get_setting_value(key=SystemSettingsEnum.VIRTUAL_USERS, default=[]) + real_users = UserManagerApi.list_users({"fields": "username", "exact_lookups": ",".join(users)})["results"] + real_users = [user["username"] for user in real_users] + return list(set(real_users) - set(virtual_users)) diff --git a/dbm-ui/backend/db_services/mongodb/permission/db_authorize/dataclass.py b/dbm-ui/backend/db_services/mongodb/permission/db_authorize/dataclass.py index 1bbb4b6f09..1e9eab1d4e 100644 --- a/dbm-ui/backend/db_services/mongodb/permission/db_authorize/dataclass.py +++ b/dbm-ui/backend/db_services/mongodb/permission/db_authorize/dataclass.py @@ -12,10 +12,9 @@ from dataclasses import dataclass from typing import Dict, List -from backend.db_services.dbpermission.constants import EXCEL_DIVIDER, AuthorizeExcelHeader +from backend.db_services.dbpermission.constants import AuthorizeExcelHeader from backend.db_services.dbpermission.db_authorize.dataclass import AuthorizeMeta as BaseAuthorizeMeta from backend.db_services.dbpermission.db_authorize.dataclass import ExcelAuthorizeMeta as BaseExcelAuthorizeMeta -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord @dataclass @@ -24,30 +23,6 @@ class MongoDBAuthorizeMeta(BaseAuthorizeMeta): mongo_users: List[Dict] = None # 多个账号规则 - @classmethod - def from_excel_data(cls, excel_data: Dict, cluster_type: str) -> "MongoDBAuthorizeMeta": - """从权限excel数据解析为AuthorizeMeta""" - return cls( - user=excel_data[AuthorizeExcelHeader.USER], - access_dbs=excel_data[AuthorizeExcelHeader.ACCESS_DBS].split(EXCEL_DIVIDER), - target_instances=excel_data[AuthorizeExcelHeader.TARGET_INSTANCES].split(EXCEL_DIVIDER), - cluster_type=cluster_type, - ) - - @classmethod - def serializer_record_data(cls, record_queryset: List[AuthorizeRecord]) -> List[Dict]: - """将授权记录进行序列化""" - record_data_list = [ - { - AuthorizeExcelHeader.USER: record.user, - AuthorizeExcelHeader.TARGET_INSTANCES: record.target_instances, - AuthorizeExcelHeader.ACCESS_DBS: record.access_dbs, - AuthorizeExcelHeader.ERROR: record.error, - } - for record in record_queryset - ] - return record_data_list - @dataclass class MongoDBExcelAuthorizeMeta(BaseExcelAuthorizeMeta): @@ -55,10 +30,6 @@ class MongoDBExcelAuthorizeMeta(BaseExcelAuthorizeMeta): @classmethod def serialize_excel_data(cls, data: Dict) -> Dict: - """将数据解析为权限excel data类的数据""" - return { - AuthorizeExcelHeader.USER: data["username"], - AuthorizeExcelHeader.TARGET_INSTANCES: EXCEL_DIVIDER.join(data["target_instances"]), - AuthorizeExcelHeader.ACCESS_DBS: EXCEL_DIVIDER.join(data["access_dbs"]), - AuthorizeExcelHeader.ERROR: data["message"], - } + excel_data = BaseExcelAuthorizeMeta.serialize_excel_data(data) + excel_data.pop(AuthorizeExcelHeader.SOURCE_IPS) + return excel_data diff --git a/dbm-ui/backend/db_services/mongodb/permission/db_authorize/handlers.py b/dbm-ui/backend/db_services/mongodb/permission/db_authorize/handlers.py index 5ffadb3e1c..325b2c94ed 100644 --- a/dbm-ui/backend/db_services/mongodb/permission/db_authorize/handlers.py +++ b/dbm-ui/backend/db_services/mongodb/permission/db_authorize/handlers.py @@ -80,7 +80,7 @@ def _pre_check_rules( def _get_user_rules_and_password_map(self, users: List[str]): """提前查询权限规则表和密码表""" - user_db__rules = AccountHandler.aggregate_user_db_privileges(self.bk_biz_id, AccountType.MONGODB) + user_db__rules = AccountHandler.aggregate_user_db_rules(self.bk_biz_id, AccountType.MONGODB) params = {"bk_biz_id": self.bk_biz_id, "users": users, "cluster_type": AccountType.MONGODB.value} user_password_data = DBPrivManagerApi.get_account_include_password(params)["items"] diff --git a/dbm-ui/backend/db_services/mysql/open_area/handlers.py b/dbm-ui/backend/db_services/mysql/open_area/handlers.py index 767cb0fcc0..605791d6d3 100644 --- a/dbm-ui/backend/db_services/mysql/open_area/handlers.py +++ b/dbm-ui/backend/db_services/mysql/open_area/handlers.py @@ -151,8 +151,13 @@ def __get_openarea_rules_set(cls, config, config_data, operator, cluster_id__clu ) # 根据用户名和db将授权规则分批 user__dbs_rules: Dict[str, List[str]] = defaultdict(list) + user__privileges: Dict[str, List[Dict]] = defaultdict(list) for rule_data in authorize_rules["items"]: - user__dbs_rules[rule_data["account"]["user"]].extend([r["dbname"] for r in rule_data["rules"]]) + user = rule_data["account"]["user"] + user__dbs_rules[user].extend([r["dbname"] for r in rule_data["rules"]]) + user__privileges[user].extend( + [{"user": user, "access_db": r["dbname"], "priv": r["priv"]} for r in rule_data["rules"]] + ) # 根据当前的规则生成授权数据 authorize_details: List[Dict[str, Any]] = [ { @@ -162,6 +167,7 @@ def __get_openarea_rules_set(cls, config, config_data, operator, cluster_id__clu "source_ips": data["authorize_ips"], "target_instances": [cluster_id__cluster[data["cluster_id"]].immute_domain], "access_dbs": user__dbs_rules[user], + "privileges": user__privileges[user], "account_rules": [ {"bk_biz_id": config.bk_biz_id, "dbname": dbname} for dbname in user__dbs_rules[user] ], diff --git a/dbm-ui/backend/db_services/mysql/permission/authorize/dataclass.py b/dbm-ui/backend/db_services/mysql/permission/authorize/dataclass.py index 8092cf74da..df2e0207e1 100644 --- a/dbm-ui/backend/db_services/mysql/permission/authorize/dataclass.py +++ b/dbm-ui/backend/db_services/mysql/permission/authorize/dataclass.py @@ -10,59 +10,24 @@ """ from dataclasses import dataclass -from typing import Dict, List +from typing import Dict -from backend.db_services.dbpermission.constants import EXCEL_DIVIDER, AuthorizeExcelHeader from backend.db_services.dbpermission.db_authorize.dataclass import AuthorizeMeta as BaseAuthorizeMeta from backend.db_services.dbpermission.db_authorize.dataclass import ExcelAuthorizeMeta as BaseExcelAuthorizeMeta -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord @dataclass class MySQLAuthorizeMeta(BaseAuthorizeMeta): """授权元信息的数据模型""" - @classmethod - def from_excel_data(cls, excel_data: Dict, cluster_type: str) -> "MySQLAuthorizeMeta": - """从权限excel数据解析为AuthorizeMeta""" - return cls( - user=excel_data[AuthorizeExcelHeader.USER], - access_dbs=excel_data[AuthorizeExcelHeader.ACCESS_DBS].split(EXCEL_DIVIDER), - source_ips=MySQLExcelAuthorizeMeta.format_ip(excel_data[AuthorizeExcelHeader.SOURCE_IPS]), - target_instances=excel_data[AuthorizeExcelHeader.TARGET_INSTANCES].split(EXCEL_DIVIDER), - cluster_type=cluster_type, - ) - - @classmethod - def serializer_record_data(cls, record_queryset: List[AuthorizeRecord]) -> List[Dict]: - """从授权记录解析为data数据""" - record_data_list = [ - { - AuthorizeExcelHeader.USER: record.user, - AuthorizeExcelHeader.SOURCE_IPS: record.source_ips, - AuthorizeExcelHeader.TARGET_INSTANCES: record.target_instances, - AuthorizeExcelHeader.ACCESS_DBS: record.access_dbs, - AuthorizeExcelHeader.ERROR: record.error, - } - for record in record_queryset - ] - return record_data_list + pass @dataclass class MySQLExcelAuthorizeMeta(BaseExcelAuthorizeMeta): """excel授权信息的数据模型""" - @classmethod - def serialize_excel_data(cls, data: Dict) -> Dict: - """将数据解析为权限excel data类的数据""" - return { - AuthorizeExcelHeader.USER: data["user"], - AuthorizeExcelHeader.SOURCE_IPS: EXCEL_DIVIDER.join(data["source_ips"]), - AuthorizeExcelHeader.TARGET_INSTANCES: EXCEL_DIVIDER.join(data["target_instances"]), - AuthorizeExcelHeader.ACCESS_DBS: EXCEL_DIVIDER.join([rule["dbname"] for rule in data["account_rules"]]), - AuthorizeExcelHeader.ERROR: data["message"], - } + pass @dataclass diff --git a/dbm-ui/backend/db_services/mysql/permission/authorize/handlers.py b/dbm-ui/backend/db_services/mysql/permission/authorize/handlers.py index fc6642dd96..b2fda084e7 100644 --- a/dbm-ui/backend/db_services/mysql/permission/authorize/handlers.py +++ b/dbm-ui/backend/db_services/mysql/permission/authorize/handlers.py @@ -23,6 +23,7 @@ from backend.configuration.constants import DBType from backend.db_meta.enums import ClusterEntryType, ClusterType from backend.db_meta.models import Cluster +from backend.db_services.dbpermission.db_account.handlers import AccountHandler from backend.db_services.dbpermission.db_authorize.dataclass import AuthorizeMeta, ExcelAuthorizeMeta from backend.db_services.dbpermission.db_authorize.handlers import AuthorizeHandler from backend.db_services.ipchooser.query.resource import ResourceQueryHelper @@ -52,18 +53,27 @@ def pre_check_excel_rules(self, excel_authorize: ExcelAuthorizeMeta, **kwargs) - """sqlserver的excel导入授权""" account_type = ClusterType.cluster_type_to_db_type(excel_authorize.cluster_type) user_info_map = self._get_user_info_map(account_type, self.bk_biz_id) - return super().pre_check_excel_rules(excel_authorize, user_info_map=user_info_map, **kwargs) + user_db_map = AccountHandler.aggregate_user_db_rules(self.bk_biz_id, account_type) + return super().pre_check_excel_rules( + excel_authorize, user_info_map=user_info_map, user_db_map=user_db_map, **kwargs + ) def pre_check_rules(self, authorize: AuthorizeMeta, task_index: int = None, **kwargs) -> Dict: # 如果没有user_info_map,则请求一次。 account_type = ClusterType.cluster_type_to_db_type(authorize.cluster_type) + if not kwargs.get("user_info_map"): kwargs["user_info_map"] = super()._get_user_info_map(account_type, self.bk_biz_id) + if not kwargs.get("user_db_map"): + kwargs["user_db_map"] = AccountHandler.aggregate_user_db_rules(self.bk_biz_id, account_type) pre_check_data = super().pre_check_rules(authorize, task_index, **kwargs) account_id = pre_check_data["authorize_data"]["account_id"] + # 补充授权详情 + user, access_dbs, user_db_map = authorize.user, authorize.access_dbs, kwargs["user_db_map"] + privileges = [{"user": user, "access_db": db, "priv": user_db_map[user][db]} for db in access_dbs] # mysql的授权数据和输入的authorize保持一致 - pre_check_data["authorize_data"] = {"account_id": account_id, **authorize.to_dict()} + pre_check_data["authorize_data"] = {"account_id": account_id, "privileges": privileges, **authorize.to_dict()} return pre_check_data def _pre_check_rules( @@ -72,6 +82,7 @@ def _pre_check_rules( """前置校验的具体实现逻辑""" account_rules = [{"bk_biz_id": self.bk_biz_id, "dbname": dbname} for dbname in authorize.access_dbs] source_ips = [item["ip"] if isinstance(item, dict) else item for item in authorize.source_ips] + authorize_data = { "bk_biz_id": self.bk_biz_id, "operator": self.operator, diff --git a/dbm-ui/backend/db_services/mysql/permission/db_account/handlers.py b/dbm-ui/backend/db_services/mysql/permission/db_account/handlers.py index 702e0b5ce4..09e0734a8b 100644 --- a/dbm-ui/backend/db_services/mysql/permission/db_account/handlers.py +++ b/dbm-ui/backend/db_services/mysql/permission/db_account/handlers.py @@ -53,7 +53,7 @@ def has_high_risk_privileges(self, rule_sets): @param rule_sets: 授权列表,数据结构与MySQLPrivManagerApi.authorize_rules接口相同 """ risk_priv_set = set(PrivilegeType.MySQL.GLOBAL.get_values()) - user_db__rules = self.aggregate_user_db_privileges(self.bk_biz_id, self.account_type) + user_db__rules = self.aggregate_user_db_rules(self.bk_biz_id, self.account_type) # 判断是否有高危权限 for rule_set in rule_sets: for rule in rule_set["account_rules"]: diff --git a/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/dataclass.py b/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/dataclass.py index 7ce0cf14c3..ef7f615b59 100644 --- a/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/dataclass.py +++ b/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/dataclass.py @@ -12,10 +12,9 @@ from dataclasses import dataclass from typing import Dict, List -from backend.db_services.dbpermission.constants import EXCEL_DIVIDER, AuthorizeExcelHeader +from backend.db_services.dbpermission.constants import AuthorizeExcelHeader from backend.db_services.dbpermission.db_authorize.dataclass import AuthorizeMeta as BaseAuthorizeMeta from backend.db_services.dbpermission.db_authorize.dataclass import ExcelAuthorizeMeta as BaseExcelAuthorizeMeta -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord @dataclass @@ -24,30 +23,6 @@ class SQLServerDBAuthorizeMeta(BaseAuthorizeMeta): sqlserver_users: List[Dict] = None # 多个账号规则 - @classmethod - def from_excel_data(cls, excel_data: Dict, cluster_type: str) -> "SQLServerDBAuthorizeMeta": - """从权限excel数据解析为AuthorizeMeta""" - return cls( - user=excel_data[AuthorizeExcelHeader.USER], - access_dbs=excel_data[AuthorizeExcelHeader.ACCESS_DBS].split(EXCEL_DIVIDER), - target_instances=excel_data[AuthorizeExcelHeader.TARGET_INSTANCES].split(EXCEL_DIVIDER), - cluster_type=cluster_type, - ) - - @classmethod - def serializer_record_data(cls, record_queryset: List[AuthorizeRecord]) -> List[Dict]: - """将授权记录进行序列化""" - record_data_list = [ - { - AuthorizeExcelHeader.USER: record.user, - AuthorizeExcelHeader.TARGET_INSTANCES: record.target_instances, - AuthorizeExcelHeader.ACCESS_DBS: record.access_dbs, - AuthorizeExcelHeader.ERROR: record.error, - } - for record in record_queryset - ] - return record_data_list - @dataclass class SQLServerExcelAuthorizeMeta(BaseExcelAuthorizeMeta): @@ -55,10 +30,6 @@ class SQLServerExcelAuthorizeMeta(BaseExcelAuthorizeMeta): @classmethod def serialize_excel_data(cls, data: Dict) -> Dict: - """将数据解析为权限excel data类的数据""" - return { - AuthorizeExcelHeader.USER: data["username"], - AuthorizeExcelHeader.TARGET_INSTANCES: EXCEL_DIVIDER.join(data["target_instances"]), - AuthorizeExcelHeader.ACCESS_DBS: EXCEL_DIVIDER.join(data["access_dbs"]), - AuthorizeExcelHeader.ERROR: data["message"], - } + excel_data = BaseExcelAuthorizeMeta.serialize_excel_data(data) + excel_data.pop(AuthorizeExcelHeader.SOURCE_IPS) + return excel_data diff --git a/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/handlers.py b/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/handlers.py index 6a59e19278..39a5550720 100644 --- a/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/handlers.py +++ b/dbm-ui/backend/db_services/sqlserver/permission/db_authorize/handlers.py @@ -69,7 +69,7 @@ def _pre_check_rules( def pre_check_excel_rules(self, excel_authorize: ExcelAuthorizeMeta, **kwargs) -> Dict: """sqlserver的excel导入授权""" - user_db__rules = AccountHandler.aggregate_user_db_privileges(self.bk_biz_id, self.account_type) + user_db__rules = AccountHandler.aggregate_user_db_rules(self.bk_biz_id, self.account_type) user_info_map = self._get_user_info_map(self.account_type, self.bk_biz_id) return super().pre_check_excel_rules( excel_authorize, user_info_map=user_info_map, user_db__rules=user_db__rules, **kwargs @@ -77,7 +77,7 @@ def pre_check_excel_rules(self, excel_authorize: ExcelAuthorizeMeta, **kwargs) - def multi_user_pre_check_rules(self, authorize: SQLServerDBAuthorizeMeta, **kwargs): """多个账号的前置检查,适合sqlserver的授权""" - user_db__rules = AccountHandler.aggregate_user_db_privileges(self.bk_biz_id, self.account_type) + user_db__rules = AccountHandler.aggregate_user_db_rules(self.bk_biz_id, self.account_type) user_info_map = self._get_user_info_map(self.account_type, self.bk_biz_id) authorize_check_result = self._multi_user_pre_check_rules( authorize, users_key="sqlserver_users", user_db__rules=user_db__rules, user_info_map=user_info_map diff --git a/dbm-ui/backend/dbm_init/json_files/itsm/itsm_dbm.json b/dbm-ui/backend/dbm_init/json_files/itsm/itsm_dbm.json index ca521db105..1060393861 100644 --- a/dbm-ui/backend/dbm_init/json_files/itsm/itsm_dbm.json +++ b/dbm-ui/backend/dbm_init/json_files/itsm/itsm_dbm.json @@ -26,11 +26,11 @@ "supervise_type": "EMPTY", "supervisor": "", "engine_version": "PIPELINE_V1", - "version_number": "20220929163003", + "version_number": "20240830152330", "table": { "id": 20, "is_deleted": false, - "name": "\u9ed8\u8ba4", + "name": "\u9ed8\u8ba4_20230807183156", "desc": "\u9ed8\u8ba4\u57fa\u7840\u6a21\u578b", "version": "EMPTY", "fields": [ @@ -149,20 +149,7 @@ "show_type": 1, "show_conditions": {}, "regex": "EMPTY", - "regex_config": { - "rule": { - "expressions": [ - { - "condition": "", - "key": "", - "source": "field", - "type": "SELECT", - "value": "" - } - ], - "type": "and" - } - }, + "regex_config": {}, "custom_regex": "", "desc": "\u8bf7\u9009\u62e9\u4f18\u5148\u7ea7", "tips": "", @@ -231,13 +218,13 @@ "task_schemas": [], "creator": "", "updated_by": "", - "workflow_id": 268, + "workflow_id": 368, "version_message": "", "states": { - "1904": { - "workflow": 268, - "id": 1904, - "key": 1904, + "1482": { + "workflow": 368, + "id": 1482, + "key": 1482, "name": "\u5f00\u59cb", "desc": "", "distribute_type": "PROCESS", @@ -269,16 +256,16 @@ "is_multi": false, "is_allow_skip": false, "creator": null, - "create_at": "2022-04-06 11:22:58", - "updated_by": "wolkanwang", - "update_at": "2022-04-06 14:37:28", + "create_at": "2024-08-30 14:58:44", + "updated_by": null, + "update_at": "2024-08-30 14:58:44", "end_at": null, "is_first_state": false }, - "1905": { - "workflow": 268, - "id": 1905, - "key": 1905, + "1483": { + "workflow": 368, + "id": 1483, + "key": 1483, "name": "\u63d0\u5355", "desc": "", "distribute_type": "PROCESS", @@ -289,7 +276,14 @@ "is_builtin": true, "variables": { "inputs": [], - "outputs": [] + "outputs": [ + { + "key": "approve_mode", + "type": "STRING", + "source": "field", + "state": 1483 + } + ] }, "tag": "DEFAULT", "processors_type": "OPEN", @@ -308,10 +302,13 @@ "is_draft": false, "is_terminable": false, "fields": [ - 4126, - 4131, - 4133, - 4439 + 2648, + 2655, + 2656, + 2650, + 2654, + 2657, + 2658 ], "type": "NORMAL", "api_instance_id": 0, @@ -320,21 +317,21 @@ "is_multi": false, "is_allow_skip": false, "creator": null, - "create_at": "2022-04-06 11:22:58", + "create_at": "2024-08-30 14:58:44", "updated_by": "admin", - "update_at": "2022-09-29 11:49:13", + "update_at": "2024-08-30 15:22:16", "end_at": null, "is_first_state": true }, - "1906": { - "workflow": 268, - "id": 1906, - "key": 1906, + "1484": { + "workflow": 368, + "id": 1484, + "key": 1484, "name": "\u7ed3\u675f", "desc": "", "distribute_type": "PROCESS", "axis": { - "x": 860, + "x": 915, "y": 150 }, "is_builtin": true, @@ -361,21 +358,21 @@ "is_multi": false, "is_allow_skip": false, "creator": null, - "create_at": "2022-04-06 11:22:58", - "updated_by": "wolkanwang", - "update_at": "2022-04-06 14:43:33", + "create_at": "2024-08-30 14:58:44", + "updated_by": "admin", + "update_at": "2024-08-30 15:03:21", "end_at": null, "is_first_state": false }, - "1908": { - "workflow": 268, - "id": 1908, - "key": 1908, + "1485": { + "workflow": 368, + "id": 1485, + "key": 1485, "name": "\u5ba1\u6279", - "desc": "", + "desc": "\u6216\u7b7e\u5ba1\u6279\uff1a\u591a\u4e2a\u5ba1\u6279\u4eba\u5f53\u6709\u4e00\u4e2a\u5ba1\u6279\u4eba\u5ba1\u6279\u5373\u53ef", "distribute_type": "PROCESS", "axis": { - "x": 520, + "x": 645, "y": 150 }, "is_builtin": false, @@ -484,9 +481,9 @@ "is_draft": false, "is_terminable": false, "fields": [ - 4134, - 4135, - 4136 + 2651, + 2652, + 2653 ], "type": "APPROVAL", "api_instance_id": 0, @@ -497,20 +494,157 @@ }, "is_multi": false, "is_allow_skip": false, - "creator": "wolkanwang", - "create_at": "2022-04-06 14:39:58", - "updated_by": "wolkanwang", - "update_at": "2022-04-06 14:43:31", + "creator": null, + "create_at": "2024-08-30 14:58:44", + "updated_by": "admin", + "update_at": "2024-08-30 15:05:06", + "end_at": null, + "is_first_state": false + }, + "1486": { + "workflow": 368, + "id": 1486, + "key": 1486, + "name": "\u5ba1\u6279", + "desc": "\u4f1a\u7b7e\u5ba1\u6279\uff1a\u6240\u6709\u5ba1\u6279\u4eba\u90fd\u9700\u8981\u5ba1\u6279", + "distribute_type": "PROCESS", + "axis": { + "x": 595, + "y": 230 + }, + "is_builtin": false, + "variables": { + "inputs": [], + "outputs": [ + { + "source": "global", + "state": 1486, + "type": "STRING", + "key": "deb2582f59a2b8a64dcd9c39db46cb5d", + "name": "\u5ba1\u6279\u7ed3\u679c", + "meta": { + "code": "NODE_APPROVE_RESULT", + "type": "SELECT", + "choice": [ + { + "key": "false", + "name": "\u62d2\u7edd" + }, + { + "key": "true", + "name": "\u901a\u8fc7" + } + ] + } + }, + { + "source": "global", + "state": 1486, + "type": "STRING", + "key": "c89dfde9cccbb14ec54531207808f5cd", + "name": "\u5ba1\u6279\u4eba", + "meta": { + "code": "NODE_APPROVER" + } + }, + { + "source": "global", + "state": 1486, + "type": "INT", + "key": "be943a6acf33dda4e5caf619c1969bf5", + "name": "\u5904\u7406\u4eba\u6570", + "meta": { + "code": "PROCESS_COUNT" + } + }, + { + "source": "global", + "state": 1486, + "type": "INT", + "key": "t58812863966ae6c179f7c2def340d5d", + "name": "\u901a\u8fc7\u4eba\u6570", + "meta": { + "code": "PASS_COUNT" + } + }, + { + "source": "global", + "state": 1486, + "type": "INT", + "key": "e96b4c085252060835d64162d2cad36f", + "name": "\u62d2\u7edd\u4eba\u6570", + "meta": { + "code": "REJECT_COUNT" + } + }, + { + "source": "global", + "state": 1486, + "type": "INT", + "key": "dc4e4a483fae889573e72dd778a3aa6a", + "name": "\u901a\u8fc7\u7387", + "meta": { + "code": "PASS_RATE", + "unit": "PERCENT" + } + }, + { + "source": "global", + "state": 1486, + "type": "INT", + "key": "fd622273213f240b803b0ac53bbe84cb", + "name": "\u62d2\u7edd\u7387", + "meta": { + "code": "REJECT_RATE", + "unit": "PERCENT" + } + } + ] + }, + "tag": "DEFAULT", + "processors_type": "VARIABLE", + "processors": "approver", + "assignors": "", + "assignors_type": "EMPTY", + "delivers": "", + "delivers_type": "EMPTY", + "can_deliver": false, + "extras": { + "ticket_status": { + "name": "", + "type": "keep" + } + }, + "is_draft": false, + "is_terminable": false, + "fields": [ + 2659, + 2660, + 2661 + ], + "type": "APPROVAL", + "api_instance_id": 0, + "is_sequential": false, + "finish_condition": { + "expressions": [], + "type": "or" + }, + "is_multi": true, + "is_allow_skip": false, + "creator": "admin", + "create_at": "2024-08-30 15:03:18", + "updated_by": "admin", + "update_at": "2024-08-30 15:05:39", "end_at": null, "is_first_state": false } }, "transitions": { - "2012": { - "workflow": 268, - "id": 2012, - "from_state": 1904, - "to_state": 1905, + "1126": { + "workflow": 368, + "id": 1126, + "from_state": 1482, + "to_state": 1483, "name": "", "axis": { "start": "Right", @@ -532,17 +666,54 @@ "type": "and" }, "condition_type": "default", - "creator": "system", - "create_at": "2022-04-06 11:22:58", - "updated_by": "system", - "update_at": "2022-04-06 11:22:58", + "creator": null, + "create_at": "2024-08-30 14:58:44", + "updated_by": null, + "update_at": "2024-08-30 14:58:44", "end_at": null }, - "2014": { - "workflow": 268, - "id": 2014, - "from_state": 1905, - "to_state": 1908, + "1127": { + "workflow": 368, + "id": 1127, + "from_state": 1483, + "to_state": 1485, + "name": "\u6216\u7b7e", + "axis": { + "start": "Right", + "end": "Left" + }, + "condition": { + "expressions": [ + { + "checkInfo": false, + "expressions": [ + { + "choiceList": [], + "condition": "==", + "key": "approve_mode", + "source": "field", + "type": "STRING", + "value": "0", + "meta": {} + } + ], + "type": "and" + } + ], + "type": "and" + }, + "condition_type": "by_field", + "creator": null, + "create_at": "2024-08-30 14:58:44", + "updated_by": "admin", + "update_at": "2024-08-30 15:04:13", + "end_at": null + }, + "1128": { + "workflow": 368, + "id": 1128, + "from_state": 1485, + "to_state": 1484, "name": "\u9ed8\u8ba4", "axis": { "start": "Right", @@ -564,22 +735,59 @@ "type": "and" }, "condition_type": "default", - "creator": "wolkanwang", - "create_at": "2022-04-06 14:40:02", - "updated_by": "wolkanwang", - "update_at": "2022-04-06 14:40:02", + "creator": null, + "create_at": "2024-08-30 14:58:44", + "updated_by": null, + "update_at": "2024-08-30 14:58:44", "end_at": null }, - "2015": { - "workflow": 268, - "id": 2015, - "from_state": 1908, - "to_state": 1906, - "name": "\u9ed8\u8ba4", + "1129": { + "workflow": 368, + "id": 1129, + "from_state": 1483, + "to_state": 1486, + "name": "\u4f1a\u7b7e", "axis": { "start": "Right", "end": "Left" }, + "condition": { + "expressions": [ + { + "checkInfo": false, + "expressions": [ + { + "choiceList": [], + "condition": "==", + "key": "approve_mode", + "source": "field", + "type": "STRING", + "value": "1", + "meta": {} + } + ], + "type": "and" + } + ], + "type": "and" + }, + "condition_type": "by_field", + "creator": "admin", + "create_at": "2024-08-30 15:03:18", + "updated_by": "admin", + "update_at": "2024-08-30 15:04:47", + "end_at": null + }, + "1130": { + "workflow": 368, + "id": 1130, + "from_state": 1486, + "to_state": 1484, + "name": "\u9ed8\u8ba4", + "axis": { + "start": "Right", + "end": "Bottom" + }, "condition": { "expressions": [ { @@ -596,17 +804,17 @@ "type": "and" }, "condition_type": "default", - "creator": "wolkanwang", - "create_at": "2022-04-06 14:40:09", - "updated_by": "wolkanwang", - "update_at": "2022-04-06 14:40:09", + "creator": "admin", + "create_at": "2024-08-30 15:04:16", + "updated_by": "admin", + "update_at": "2024-08-30 15:04:16", "end_at": null } }, "triggers": [], "fields": { - "4126": { - "id": 4126, + "2648": { + "id": 2648, "is_deleted": false, "is_builtin": true, "is_readonly": false, @@ -633,54 +841,12 @@ "choice": [], "related_fields": {}, "meta": {}, - "workflow_id": 268, + "workflow_id": 368, "state_id": "", "source": "TABLE" }, - "4131": { - "id": 4131, - "is_deleted": false, - "is_builtin": false, - "is_readonly": false, - "is_valid": true, - "display": false, - "source_type": "CUSTOM", - "source_uri": "", - "api_instance_id": 0, - "kv_relation": { - "key": "bk_biz_id", - "name": "bk_biz_name" - }, - "type": "STRING", - "key": "bk_biz_id", - "name": "\u4e1a\u52a1", - "layout": "COL_12", - "validate_type": "REQUIRE", - "show_type": 1, - "show_conditions": {}, - "regex": "EMPTY", - "regex_config": { - "rule": { - "expressions": [], - "type": "and" - } - }, - "custom_regex": "", - "desc": "\u8bf7\u586b\u5199\u4e1a\u52a1id\uff08bk_biz_id\uff09", - "tips": "", - "is_tips": false, - "default": "", - "choice": [], - "related_fields": { - "rely_on": [] - }, - "meta": {}, - "workflow_id": 268, - "state_id": 1905, - "source": "CUSTOM" - }, - "4133": { - "id": 4133, + "2650": { + "id": 2650, "is_deleted": false, "is_builtin": false, "is_readonly": false, @@ -720,12 +886,12 @@ "choice": [], "related_fields": {}, "meta": {}, - "workflow_id": 268, - "state_id": 1905, + "workflow_id": 368, + "state_id": 1483, "source": "CUSTOM" }, - "4134": { - "id": 4134, + "2651": { + "id": 2651, "is_deleted": false, "is_builtin": false, "is_readonly": false, @@ -763,12 +929,12 @@ "meta": { "code": "APPROVE_RESULT" }, - "workflow_id": 268, - "state_id": 1908, + "workflow_id": 368, + "state_id": 1485, "source": "CUSTOM" }, - "4135": { - "id": 4135, + "2652": { + "id": 2652, "is_deleted": false, "is_builtin": false, "is_readonly": false, @@ -805,12 +971,12 @@ "choice": [], "related_fields": {}, "meta": {}, - "workflow_id": 268, - "state_id": 1908, + "workflow_id": 368, + "state_id": 1485, "source": "CUSTOM" }, - "4136": { - "id": 4136, + "2653": { + "id": 2653, "is_deleted": false, "is_builtin": false, "is_readonly": false, @@ -847,12 +1013,12 @@ "choice": [], "related_fields": {}, "meta": {}, - "workflow_id": 268, - "state_id": 1908, + "workflow_id": 368, + "state_id": 1485, "source": "CUSTOM" }, - "4439": { - "id": 4439, + "2654": { + "id": 2654, "is_deleted": false, "is_builtin": false, "is_readonly": false, @@ -864,9 +1030,9 @@ "kv_relation": {}, "type": "TEXT", "key": "summary", - "name": "\u6982\u89c8", + "name": "\u5907\u6ce8", "layout": "COL_12", - "validate_type": "REQUIRE", + "validate_type": "OPTION", "show_type": 1, "show_conditions": {}, "regex": "EMPTY", @@ -892,8 +1058,325 @@ "choice": [], "related_fields": {}, "meta": {}, - "workflow_id": 268, - "state_id": 1905, + "workflow_id": 368, + "state_id": 1483, + "source": "CUSTOM" + }, + "2655": { + "id": 2655, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": false, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "STRING", + "key": "app", + "name": "\u4e1a\u52a1", + "layout": "COL_12", + "validate_type": "REQUIRE", + "show_type": 1, + "show_conditions": {}, + "regex": "EMPTY", + "regex_config": { + "rule": { + "expressions": [ + { + "condition": "", + "key": "", + "source": "field", + "type": "", + "value": "" + } + ], + "type": "and" + } + }, + "custom_regex": "", + "desc": "", + "tips": "", + "is_tips": false, + "default": "", + "choice": [], + "related_fields": {}, + "meta": {}, + "workflow_id": 368, + "state_id": 1483, + "source": "CUSTOM" + }, + "2656": { + "id": 2656, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": false, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "TEXT", + "key": "domain", + "name": "\u57df\u540d", + "layout": "COL_12", + "validate_type": "OPTION", + "show_type": 1, + "show_conditions": {}, + "regex": "EMPTY", + "regex_config": { + "rule": { + "expressions": [ + { + "condition": "", + "key": "", + "source": "field", + "type": "", + "value": "" + } + ], + "type": "and" + } + }, + "custom_regex": "", + "desc": "", + "tips": "", + "is_tips": false, + "default": "", + "choice": [], + "related_fields": {}, + "meta": {}, + "workflow_id": 368, + "state_id": 1483, + "source": "CUSTOM" + }, + "2657": { + "id": 2657, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": false, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "STRING", + "key": "approve_mode", + "name": "\u5ba1\u6279\u6a21\u5f0f", + "layout": "COL_12", + "validate_type": "OPTION", + "show_type": 0, + "show_conditions": { + "type": "and", + "expressions": [ + { + "key": "app", + "condition": "!=", + "value": "\"\"", + "type": "STRING" + } + ] + }, + "regex": "EMPTY", + "regex_config": { + "rule": { + "expressions": [ + { + "condition": "", + "key": "", + "source": "field", + "type": "", + "value": "" + } + ], + "type": "and" + } + }, + "custom_regex": "", + "desc": "0: \u6216\u7b7e\u6a21\u5f0f\n1: \u4f1a\u7b7e\u6a21\u5f0f", + "tips": "", + "is_tips": false, + "default": "0", + "choice": [], + "related_fields": {}, + "meta": {}, + "workflow_id": 368, + "state_id": 1483, + "source": "CUSTOM" + }, + "2658": { + "id": 2658, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": false, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "LINK", + "key": "ticket_url", + "name": "\u9700\u6c42\u8be6\u60c5", + "layout": "COL_12", + "validate_type": "REQUIRE", + "show_type": 1, + "show_conditions": {}, + "regex": "EMPTY", + "regex_config": { + "rule": { + "expressions": [ + { + "condition": "", + "key": "", + "source": "field", + "type": "", + "value": "" + } + ], + "type": "and" + } + }, + "custom_regex": "", + "desc": "", + "tips": "", + "is_tips": false, + "default": "", + "choice": [], + "related_fields": {}, + "meta": {}, + "workflow_id": 368, + "state_id": 1483, + "source": "CUSTOM" + }, + "2659": { + "id": 2659, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": true, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "RADIO", + "key": "be937ddce3ec8435c96a8c313bae4836", + "name": "\u5ba1\u6279\u610f\u89c1", + "layout": "COL_6", + "validate_type": "REQUIRE", + "show_type": 1, + "show_conditions": {}, + "regex": "EMPTY", + "regex_config": {}, + "custom_regex": "", + "desc": "", + "tips": "", + "is_tips": false, + "default": "true", + "choice": [ + { + "key": "true", + "name": "\u901a\u8fc7" + }, + { + "key": "false", + "name": "\u62d2\u7edd" + } + ], + "related_fields": {}, + "meta": { + "code": "APPROVE_RESULT" + }, + "workflow_id": 368, + "state_id": 1486, + "source": "CUSTOM" + }, + "2660": { + "id": 2660, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": false, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "TEXT", + "key": "zf29ac1ab6e54d18ddc743a2ffa4ecf3", + "name": "\u5907\u6ce8", + "layout": "COL_12", + "validate_type": "OPTION", + "show_type": 0, + "show_conditions": { + "expressions": [ + { + "value": "false", + "type": "RADIO", + "condition": "==", + "key": "be937ddce3ec8435c96a8c313bae4836" + } + ], + "type": "and" + }, + "regex": "EMPTY", + "regex_config": {}, + "custom_regex": "", + "desc": "", + "tips": "", + "is_tips": false, + "default": "", + "choice": [], + "related_fields": {}, + "meta": {}, + "workflow_id": 368, + "state_id": 1486, + "source": "CUSTOM" + }, + "2661": { + "id": 2661, + "is_deleted": false, + "is_builtin": false, + "is_readonly": false, + "is_valid": true, + "display": false, + "source_type": "CUSTOM", + "source_uri": "", + "api_instance_id": 0, + "kv_relation": {}, + "type": "TEXT", + "key": "d33b7919a6805e3e6f9162600b451657", + "name": "\u5907\u6ce8", + "layout": "COL_12", + "validate_type": "REQUIRE", + "show_type": 0, + "show_conditions": { + "expressions": [ + { + "value": "true", + "type": "RADIO", + "condition": "==", + "key": "be937ddce3ec8435c96a8c313bae4836" + } + ], + "type": "and" + }, + "regex": "EMPTY", + "regex_config": {}, + "custom_regex": "", + "desc": "", + "tips": "", + "is_tips": false, + "default": "", + "choice": [], + "related_fields": {}, + "meta": {}, + "workflow_id": 368, + "state_id": 1486, "source": "CUSTOM" } }, @@ -901,6 +1384,10 @@ 1 ], "extras": { + "biz_related": false, + "need_urge": false, + "urgers_type": "EMPTY", + "urgers": "", "task_settings": [] } }, diff --git a/dbm-ui/backend/flow/engine/bamboo/scene/common/account_rule_manage.py b/dbm-ui/backend/flow/engine/bamboo/scene/common/account_rule_manage.py new file mode 100644 index 0000000000..8106624fcf --- /dev/null +++ b/dbm-ui/backend/flow/engine/bamboo/scene/common/account_rule_manage.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. +Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at https://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" +import logging +from typing import Dict, Optional + +from django.utils.translation import ugettext as _ + +from backend.components import DBPrivManagerApi +from backend.db_services.dbpermission.constants import RuleActionType +from backend.db_services.dbpermission.db_authorize.models import DBRuleActionLog +from backend.flow.engine.bamboo.scene.common.builder import Builder +from backend.flow.plugins.components.collections.common.external_service import ExternalServiceComponent + +logger = logging.getLogger("flow") + + +def insert_change_record(params, data, kwargs, global_data): + DBRuleActionLog.objects.create( + operator=params["operator"], + account_id=params["account_id"], + rule_id=params["rule_id"], + action_type=params["action"], + ) + + +class AccountRulesFlows(object): + """ + 账号权限模板管理流程(修改,删除) + """ + + def __init__(self, root_id: str, data: Optional[Dict]): + """ + @param root_id : 任务流程定义的root_id + @param data : 单据传递参数 + """ + + self.root_id = root_id + self.data = data + + def modify_account_rule(self): + """定义mysql账号修改流程""" + account_modify_rules = Builder(root_id=self.root_id, data=self.data) + account_modify_rules.add_act( + act_name=_("账号规则模板修改"), + act_component_code=ExternalServiceComponent.code, + kwargs={ + "params": {**self.data, "action": RuleActionType.CHANGE}, + "api_import_path": DBPrivManagerApi.__module__, + "api_import_module": "DBPrivManagerApi", + "api_call_func": "modify_account_rule", + "success_callback_path": f"{insert_change_record.__module__}.{insert_change_record.__name__}", + }, + ) + account_modify_rules.run_pipeline() + + def delete_account_rule(self): + """定义mysql账号删除流程""" + account_delete_rules = Builder(root_id=self.root_id, data=self.data) + account_delete_rules.add_act( + act_name=_("账号规则模板删除"), + act_component_code=ExternalServiceComponent.code, + kwargs={ + "params": {**self.data, "action": RuleActionType.DELETE}, + "api_import_path": DBPrivManagerApi.__module__, + "api_import_module": "DBPrivManagerApi", + "api_call_func": "delete_account_rule", + "success_callback_path": f"{insert_change_record.__module__}.{insert_change_record.__name__}", + }, + ) + account_delete_rules.run_pipeline() diff --git a/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_authorize_rules.py b/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_authorize_rules.py index df2504e1bd..4c13440096 100644 --- a/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_authorize_rules.py +++ b/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_authorize_rules.py @@ -15,11 +15,12 @@ from backend.flow.engine.bamboo.scene.common.builder import Builder from backend.flow.plugins.components.collections.mysql.authorize_rules import AuthorizeRulesComponent +from backend.flow.plugins.components.collections.mysql.clone_rules import CloneRulesComponent logger = logging.getLogger("flow") -class MySQLAuthorizeRules(object): +class MySQLAuthorizeRulesFlows(object): """ 授权mysql权限的流程抽象类 todo 后续需要兼容跨云管理 bk_cloud_id @@ -42,3 +43,12 @@ def authorize_mysql_rules(self): act_name=_("添加mysql规则授权"), act_component_code=AuthorizeRulesComponent.code, kwargs=self.data ) mysql_authorize_rules.run_pipeline() + + def clone_mysql_rules(self): + """定义mysql授权流程""" + + mysql_clone_rules = Builder(root_id=self.root_id, data=self.data) + mysql_clone_rules.add_act( + act_name=_("添加mysql权限克隆"), act_component_code=CloneRulesComponent.code, kwargs=self.data + ) + mysql_clone_rules.run_pipeline() diff --git a/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_clone_rules.py b/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_clone_rules.py deleted file mode 100644 index a364ab6364..0000000000 --- a/dbm-ui/backend/flow/engine/bamboo/scene/mysql/mysql_clone_rules.py +++ /dev/null @@ -1,43 +0,0 @@ -# -*- coding: utf-8 -*- -""" -TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. -Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. -Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. -You may obtain a copy of the License at https://opensource.org/licenses/MIT -Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on -an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. -""" -import logging -from typing import Dict, Optional - -from django.utils.translation import ugettext as _ - -from backend.flow.engine.bamboo.scene.common.builder import Builder -from backend.flow.plugins.components.collections.mysql.clone_rules import CloneRulesComponent - -logger = logging.getLogger("flow") - - -class MySQLCloneRules(object): - """ - mysql权限克隆的流程抽象类 - """ - - def __init__(self, root_id: str, data: Optional[Dict]): - """ - @param root_id : 任务流程定义的root_id - @param data : 单据传递参数 - """ - - self.root_id = root_id - self.data = data - - def clone_mysql_rules(self): - """定义mysql授权流程""" - - mysql_clone_rules = Builder(root_id=self.root_id, data=self.data) - mysql_clone_rules.add_act( - act_name=_("添加mysql权限克隆"), act_component_code=CloneRulesComponent.code, kwargs=self.data - ) - mysql_clone_rules.run_pipeline() diff --git a/dbm-ui/backend/flow/engine/controller/mysql.py b/dbm-ui/backend/flow/engine/controller/mysql.py index e9790eed15..57b4943875 100644 --- a/dbm-ui/backend/flow/engine/controller/mysql.py +++ b/dbm-ui/backend/flow/engine/controller/mysql.py @@ -11,14 +11,14 @@ from backend.db_meta.enums import ClusterType from backend.flow.engine.bamboo.scene.cloud.mysql_machine_clear_flow import ClearMysqlMachineFlow +from backend.flow.engine.bamboo.scene.common.account_rule_manage import AccountRulesFlows from backend.flow.engine.bamboo.scene.common.download_dbactor import DownloadDbactorFlow from backend.flow.engine.bamboo.scene.common.download_file import DownloadFileFlow from backend.flow.engine.bamboo.scene.common.transfer_cluster_to_other_biz import TransferMySQLClusterToOtherBizFlow from backend.flow.engine.bamboo.scene.mysql.dbconsole import DbConsoleDumpSqlFlow from backend.flow.engine.bamboo.scene.mysql.import_sqlfile_flow import ImportSQLFlow -from backend.flow.engine.bamboo.scene.mysql.mysql_authorize_rules import MySQLAuthorizeRules +from backend.flow.engine.bamboo.scene.mysql.mysql_authorize_rules import MySQLAuthorizeRulesFlows from backend.flow.engine.bamboo.scene.mysql.mysql_checksum import MysqlChecksumFlow -from backend.flow.engine.bamboo.scene.mysql.mysql_clone_rules import MySQLCloneRules from backend.flow.engine.bamboo.scene.mysql.mysql_data_migrate_flow import MysqlDataMigrateFlow from backend.flow.engine.bamboo.scene.mysql.mysql_edit_config_flow import MysqlEditConfigFlow from backend.flow.engine.bamboo.scene.mysql.mysql_fake_sql_semantic_check import MySQLFakeSemanticCheck @@ -219,7 +219,7 @@ def mysql_single_enable_scene(self): def mysql_authorize_rules(self): """授权mysql权限场景""" - flow = MySQLAuthorizeRules(root_id=self.root_id, data=self.ticket_data) + flow = MySQLAuthorizeRulesFlows(root_id=self.root_id, data=self.ticket_data) flow.authorize_mysql_rules() def mysql_clone_rules(self): @@ -229,9 +229,21 @@ def mysql_clone_rules(self): - 客户端权限克隆 """ - flow = MySQLCloneRules(root_id=self.root_id, data=self.ticket_data) + flow = MySQLAuthorizeRulesFlows(root_id=self.root_id, data=self.ticket_data) flow.clone_mysql_rules() + def mysql_account_rules_change(self): + """修改mysql账号规则模板场景""" + + flow = AccountRulesFlows(root_id=self.root_id, data=self.ticket_data) + flow.modify_account_rule() + + def mysql_account_rules_delete(self): + """修改mysql账号规则模板场景""" + + flow = AccountRulesFlows(root_id=self.root_id, data=self.ticket_data) + flow.delete_account_rule() + def mysql_proxy_add_scene(self): """ 添加mysql_proxy实例场景(新flow编排) diff --git a/dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rules.py b/dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rules.py index fe89a44211..3ecb48eebf 100644 --- a/dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rules.py +++ b/dbm-ui/backend/flow/plugins/components/collections/mysql/authorize_rules.py @@ -19,7 +19,10 @@ from backend import env from backend.components.mysql_priv_manager.client import DBPrivManagerApi from backend.configuration.constants import DBType -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord +from backend.db_services.dbpermission.constants import RuleActionType +from backend.db_services.dbpermission.db_account.handlers import AccountHandler +from backend.db_services.dbpermission.db_authorize.models import DBRuleActionLog +from backend.flow.engine.bamboo.engine import BambooEngine from backend.flow.plugins.components.collections.common.base_service import BaseService from backend.ticket.constants import TicketType @@ -29,7 +32,8 @@ class AuthorizeRules(BaseService): """根据定义的用户规则模板进行授权""" - def _generate_rule_desc(self, authorize_data): + @staticmethod + def _generate_rule_desc(authorize_data): # 生成当前规则的描述细则 rules_product: List[Tuple[Any, ...]] = list( itertools.product( @@ -47,6 +51,27 @@ def _generate_rule_desc(self, authorize_data): ) return rules_description + def _generate_rule_logs(self, bk_biz_id, account_type, operator, authorize_data_list): + # 如果该节点是重试,则无需重复记录 + root_id, node_id = self.extra_log["root_id"], self.extra_log["node_id"] + if BambooEngine(root_id).get_node_short_histories(node_id): + return + + # 对授权的规则进行授权记录 + user__db_rules: Dict[str, Dict] = AccountHandler.aggregate_user_db_rules(bk_biz_id, account_type, rule_key="") + auth_logs: List[DBRuleActionLog] = [] + for data in authorize_data_list: + for db in data["access_dbs"]: + rule = user__db_rules[data["user"]][db] + log = DBRuleActionLog( + account_id=rule["account_id"], + rule_id=rule["id"], + operator=operator, + action_type=RuleActionType.AUTH, + ) + auth_logs.append(log) + DBRuleActionLog.objects.bulk_create(auth_logs) + def _execute(self, data, parent_data, callback=None) -> bool: # kwargs就是调用授权接口传入的参数 @@ -61,37 +86,28 @@ def _execute(self, data, parent_data, callback=None) -> bool: authorize_data_list: List[Dict] = kwargs["rules_set"] authorize_success_count: int = 0 - for authorize_data in authorize_data_list: - # 将授权信息存入record - record = AuthorizeRecord( - ticket_id=ticket_id, - user=authorize_data["user"], - source_ips=",".join(authorize_data["source_ips"]), - target_instances=",".join(authorize_data["target_instances"]), - access_dbs=",".join(authorize_data["access_dbs"]), - ) + # 授权规则记录 + self._generate_rule_logs(bk_biz_id, db_type, kwargs["created_by"], authorize_data_list) + for authorize_data in authorize_data_list: # 生成规则描述 rules_description = self._generate_rule_desc(authorize_data) self.log_info(_("授权规则明细:\n{}\n").format(rules_description)) # 进行授权,无论授权是否成功,都需要将message存入record中 try: - resp = DBPrivManagerApi.authorize_rules( - params=authorize_data, raw=True, timeout=DBPrivManagerApi.TIMEOUT - ) - record.status = int(resp["code"]) == 0 - authorize_success_count += record.status - record.error = resp["message"] - self.log_info(f"{resp['message']}\n") - + resp = DBPrivManagerApi.authorize_rules(authorize_data, raw=True, timeout=DBPrivManagerApi.TIMEOUT) + if int(resp["code"]) == 0: + authorize_success_count += 1 + authorize_results = resp["message"] + self.log_info(authorize_results) except Exception as e: # pylint: disable=broad-except - record.status = False error_message = getattr(e, "message", None) or e - record.error = _("「授权接口调用异常」{}").format(error_message) - self.log_error(_("授权异常,相关信息: {}\n").format(record.error)) + authorize_results = _("「授权接口调用异常」{}").format(error_message) + self.log_error(_("授权异常,相关信息: {}\n").format(authorize_results)) - record.save() + # 作为结果输出到flow + self.set_flow_output(root_id=kwargs.get("root_id"), key="authorize_results", value=authorize_results) # 授权结果汇总 overall_result = authorize_success_count == len(authorize_data_list) @@ -106,6 +122,7 @@ def _execute(self, data, parent_data, callback=None) -> bool: len(authorize_data_list) - authorize_success_count, ) ) + # 打印授权结果详情链接下载 # 下载excel的url中,mysql和tendbcluster同用一个路由 route_type = DBType.MySQL.value if db_type == DBType.TenDBCluster else db_type diff --git a/dbm-ui/backend/iam_app/dataclass/actions.py b/dbm-ui/backend/iam_app/dataclass/actions.py index 167b5fd0fe..f864bade54 100644 --- a/dbm-ui/backend/iam_app/dataclass/actions.py +++ b/dbm-ui/backend/iam_app/dataclass/actions.py @@ -398,7 +398,7 @@ class ActionEnum: MYSQL_ADD_ACCOUNT_RULE = ActionMeta( id="mysql_add_account_rule", - name=_("MySQL账号规则创建"), + name=_("MySQL账号规则变更"), name_en="mysql_add_account_rule", type="create", related_actions=[DB_MANAGE.id], @@ -705,7 +705,7 @@ class ActionEnum: TENDBCLUSTER_ADD_ACCOUNT_RULE = ActionMeta( id="tendbcluster_add_account_rule", - name=_("TenDB Cluster 账号规则创建"), + name=_("TenDB Cluster 账号规则变更"), name_en="tendbcluster_add_account_rule", type="create", related_actions=[DB_MANAGE.id], diff --git a/dbm-ui/backend/iam_app/handlers/drf_perm/ticket.py b/dbm-ui/backend/iam_app/handlers/drf_perm/ticket.py index 6c90046dfd..ce528f1b7f 100644 --- a/dbm-ui/backend/iam_app/handlers/drf_perm/ticket.py +++ b/dbm-ui/backend/iam_app/handlers/drf_perm/ticket.py @@ -54,6 +54,8 @@ def __init__(self, ticket_type: TicketType) -> None: # instance_ids_getter = self.instance_influxdb_ids_getter if resource_meta == ResourceEnum.BUSINESS: instance_ids_getter = self.instance_biz_ids_getter + elif resource_meta in [ResourceEnum.TENDBCLUSTER_ACCOUNT, ResourceEnum.MYSQL_ACCOUNT]: + instance_ids_getter = self.instance_account_ids_getter elif action in ActionEnum.get_match_actions("tbinlogdumper"): # 对应dumper相关操作,需要根据dumper的实例ID反查出相关的集群 instance_ids_getter = self.instance_dumper_cluster_ids_getter @@ -66,6 +68,10 @@ def __init__(self, ticket_type: TicketType) -> None: def instance_biz_ids_getter(request, view): return [request.data["bk_biz_id"]] + @staticmethod + def instance_account_ids_getter(request, view): + return [request.data["details"]["account_id"]] + @staticmethod def instance_cluster_ids_getter(request, view): # 集群ID从details解析,如果没有detail(比如sql模拟执行),则直接取request.data diff --git a/dbm-ui/backend/tests/db_services/mysql/permission/test_authorize_handler.py b/dbm-ui/backend/tests/db_services/mysql/permission/test_authorize_handler.py index 77f4089bd2..985e10b79e 100644 --- a/dbm-ui/backend/tests/db_services/mysql/permission/test_authorize_handler.py +++ b/dbm-ui/backend/tests/db_services/mysql/permission/test_authorize_handler.py @@ -44,6 +44,7 @@ class TestAuthorizeHandler: handler = MySQLAuthorizeHandler(bk_biz_id=constant.BK_BIZ_ID) + @patch("backend.db_services.dbpermission.db_account.handlers.DBPrivManagerApi", DBPrivManagerApiMock) @patch("backend.db_services.dbpermission.db_authorize.handlers.DBPrivManagerApi", DBPrivManagerApiMock) @patch("backend.db_services.mysql.permission.authorize.handlers.DBPrivManagerApi", DBPrivManagerApiMock) def test_pre_check_rules(self, query_fixture): @@ -51,6 +52,7 @@ def test_pre_check_rules(self, query_fixture): authorize_result = self.handler.pre_check_rules(authorize) assert authorize_result["pre_check"] is True + @patch("backend.db_services.dbpermission.db_account.handlers.DBPrivManagerApi", DBPrivManagerApiMock) @patch("backend.db_services.dbpermission.db_authorize.handlers.DBPrivManagerApi", DBPrivManagerApiMock) @patch("backend.db_services.mysql.permission.authorize.handlers.DBPrivManagerApi", DBPrivManagerApiMock) def test_pre_check_excel_rules(self, query_fixture): diff --git a/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py b/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py index e4d8399d12..950a4ec84b 100644 --- a/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py +++ b/dbm-ui/backend/tests/flow/components/collections/mysql/permission/test_authorize_rules.py @@ -10,15 +10,16 @@ """ import logging -from typing import Any, NoReturn, Type, Union +from typing import Any, List, NoReturn, Type, Union import pytest from django.test import TestCase from pipeline.component_framework.component import Component -from backend.db_services.dbpermission.db_authorize.models import AuthorizeRecord from backend.flow.plugins.components.collections.mysql.authorize_rules import AuthorizeRulesComponent +from backend.tests.flow.components.collections.base import BaseComponentPatcher as Patcher from backend.tests.flow.components.collections.mysql.utils import MySQLComponentBaseTest +from backend.tests.mock_data.components.mysql_priv_manager import DBPrivManagerApiMock from backend.tests.mock_data.ticket.ticket_params_data import MYSQL_AUTHORIZE_FLOW_PARAMS logger = logging.getLogger("test") @@ -38,4 +39,14 @@ def component_cls(self) -> Type[Component]: return AuthorizeRulesComponent def tearDown(self) -> Union[Any, NoReturn]: - assert AuthorizeRecord.objects.filter(status=True).count() == 1 + pass + + def get_patchers(self) -> List[Patcher]: + patchers = super().get_patchers() + patchers.append( + Patcher( + target="backend.db_services.dbpermission.db_account.handlers.DBPrivManagerApi", + new=DBPrivManagerApiMock, + ) + ) + return patchers diff --git a/dbm-ui/backend/tests/mock_data/components/mysql_priv_manager.py b/dbm-ui/backend/tests/mock_data/components/mysql_priv_manager.py index 6c101cb6e7..6d5cac329d 100644 --- a/dbm-ui/backend/tests/mock_data/components/mysql_priv_manager.py +++ b/dbm-ui/backend/tests/mock_data/components/mysql_priv_manager.py @@ -8,6 +8,7 @@ an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. """ +import copy from backend.core.encrypt.constants import AsymmetricCipherConfigType from backend.core.encrypt.handlers import AsymmetricHandler @@ -58,7 +59,8 @@ def delete_account(cls, *args, **kwargs): @classmethod @raw_response def list_account_rules(cls, *args, **kwargs): - return LIST_MYSQL_ACCOUNT_RULE_RESPONSE + data = copy.deepcopy(LIST_MYSQL_ACCOUNT_RULE_RESPONSE) + return data @classmethod @raw_response diff --git a/dbm-ui/backend/tests/mock_data/db_services/mysql/permission/authorize.py b/dbm-ui/backend/tests/mock_data/db_services/mysql/permission/authorize.py index 54a38364f4..c62f670254 100644 --- a/dbm-ui/backend/tests/mock_data/db_services/mysql/permission/authorize.py +++ b/dbm-ui/backend/tests/mock_data/db_services/mysql/permission/authorize.py @@ -14,7 +14,7 @@ BK_BIZ_ID = env.DBA_APP_BK_BIZ_ID BK_USERNAME = "admin" -ACCESS_DBS = ["user", "group"] +ACCESS_DBS = ["datamain"] TARGET_INSTANCES = ["blueking.db1.com", "blueking.db2.com"] SOURCE_IPS = ["127.0.0.1", "127.0.0.2"] diff --git a/dbm-ui/backend/tests/mock_data/ticket/ticket_params_data.py b/dbm-ui/backend/tests/mock_data/ticket/ticket_params_data.py index 14502a59cb..af71ab546a 100644 --- a/dbm-ui/backend/tests/mock_data/ticket/ticket_params_data.py +++ b/dbm-ui/backend/tests/mock_data/ticket/ticket_params_data.py @@ -26,8 +26,8 @@ "bk_biz_id": 1, "operator": "admin", "user": "admin", - "access_dbs": ["user"], - "account_rules": [{"dbname": "user", "bk_biz_id": 1}], + "access_dbs": ["datamain"], + "account_rules": [{"dbname": "datamain", "bk_biz_id": 1}], "source_ips": ["127.0.0.1", "127.0.0.2"], "target_instances": ["gamedb.privtest55.blueking.db"], "cluster_type": "tendbha", diff --git a/dbm-ui/backend/ticket/builders/__init__.py b/dbm-ui/backend/ticket/builders/__init__.py index 54b21521d5..a20de5af14 100644 --- a/dbm-ui/backend/ticket/builders/__init__.py +++ b/dbm-ui/backend/ticket/builders/__init__.py @@ -129,39 +129,22 @@ def format(self): def get_params(self): self.format() # clusters只是为了给服务单详情展示的信息,不需要在单据中体现 - self.details.pop("clusters", None) + cluster_domains = [cluster["immute_domain"] for cluster in self.details.pop("clusters", {}).values()] service_id = SystemSettings.get_setting_value(SystemSettingsEnum.BK_ITSM_SERVICE_ID.value) - title = self.ticket.get_ticket_type_display() + title = _("【DBM单据审批】{}").format(self.ticket.get_ticket_type_display()) app = AppCache.objects.get(bk_biz_id=self.ticket.bk_biz_id) params = { "service_id": service_id, "creator": self.ticket.creator, "fields": [ {"key": "title", "value": title}, - {"key": "bk_biz_id", "value": self.ticket.bk_biz_id}, + {"key": "app", "value": f"{app.bk_biz_name}(#{app.bk_biz_id}, {app.db_app_abbr})"}, + {"key": "domain", "value": "\n".join(cluster_domains)}, + {"key": "summary", "value": self.ticket.remark}, {"key": "approver", "value": self.get_approvers()}, - { - "key": "summary", - "value": _("{creator}提交了{title}的单据,请查看详情后进行审批").format(creator=self.ticket.creator, title=title), - }, - ], - "dynamic_fields": [ - { - "name": _("单据链接"), - "type": "LINK", - "value": self.ticket.url, - }, - { - "name": _("需求信息"), - "type": "LINK", - "value": f"{self.ticket.url}&isFullscreen=true", - }, - { - "name": _("业务名"), - "type": "STRING", - "value": f"{app.bk_biz_name}(#{app.bk_biz_id}, {app.db_app_abbr})", - }, + {"key": "ticket_url", "value": f"{self.ticket.url}&isFullscreen=true"}, ], + "dynamic_fields": [], "meta": { "callback_url": f"{env.BK_SAAS_CALLBACK_URL}/apis/tickets/{self.ticket.id}/callback/", "state_processors": {}, diff --git a/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py b/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py index 7d7859930b..0334b0d31d 100644 --- a/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py +++ b/dbm-ui/backend/ticket/builders/mysql/mysql_authorize_rules.py @@ -33,7 +33,7 @@ class MySQLPluginInfoSerializer(serializers.Serializer): class MySQLAuthorizeDataSerializer(PreCheckAuthorizeRulesSerializer): - pass + privileges = serializers.JSONField(help_text=_("授权详情"), required=False) class MySQLAuthorizeRulesSerializer(serializers.Serializer): @@ -59,6 +59,7 @@ def validate(self, attrs): class MySQLExcelAuthorizeDataSerializer(PreCheckAuthorizeRulesSerializer): source_ips = serializers.ListField(help_text=_("ip列表"), child=serializers.CharField()) + privileges = serializers.JSONField(help_text=_("授权详情"), required=False) class MySQLExcelAuthorizeRulesSerializer(serializers.Serializer): @@ -86,7 +87,7 @@ def post_callback(self): class MySQLAuthorizeRulesFlowBuilder(BaseMySQLTicketFlowBuilder): serializer = MySQLAuthorizeRulesSerializer inner_flow_builder = MySQLAuthorizeRulesFlowParamBuilder - inner_flow_name = _("授权执行") + inner_flow_name = _("MySQL 授权执行") editable = False @property @@ -104,11 +105,10 @@ def patch_ticket_detail(self): data = cache.get(details.get("authorize_uid")) or details.get("authorize_plugin_infos") if not data: raise AuthorizeDataHasExpiredException(_("授权数据不存在/已过期,请重新提交授权表单或excel文件")) - self.ticket.update_details(rules_set=data) @builders.BuilderFactory.register(TicketType.MYSQL_EXCEL_AUTHORIZE_RULES) class MySQLExcelAuthorizeRulesFlowBuilder(MySQLAuthorizeRulesFlowBuilder): serializer = MySQLExcelAuthorizeRulesSerializer - inner_flow_name = _("Excel 授权执行") + inner_flow_name = _("MySQL Excel授权执行") diff --git a/dbm-ui/backend/ticket/builders/mysql/mysql_openarea.py b/dbm-ui/backend/ticket/builders/mysql/mysql_openarea.py index 8b4f7d60d8..25f44df7bb 100644 --- a/dbm-ui/backend/ticket/builders/mysql/mysql_openarea.py +++ b/dbm-ui/backend/ticket/builders/mysql/mysql_openarea.py @@ -43,6 +43,7 @@ class AccountRulesSerializer(serializers.Serializer): target_instances = serializers.ListField(help_text=_("目标集群列表"), child=serializers.CharField()) account_rules = serializers.ListSerializer(help_text=_("授权DB列表"), child=AccountRulesSerializer()) cluster_type = serializers.ChoiceField(help_text=_("集群类型"), choices=ClusterType.get_choices()) + privileges = serializers.JSONField(help_text=_("授权详情"), required=False) access_dbs = serializers.ListField(help_text=_("准入DB列表"), child=serializers.CharField()) cluster_id = serializers.IntegerField(help_text=_("源集群ID")) diff --git a/dbm-ui/backend/ticket/builders/mysql/mysql_priv_change.py b/dbm-ui/backend/ticket/builders/mysql/mysql_priv_change.py new file mode 100644 index 0000000000..7570c030d1 --- /dev/null +++ b/dbm-ui/backend/ticket/builders/mysql/mysql_priv_change.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. +Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at https://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" + +from django.utils.translation import ugettext as _ +from rest_framework import serializers + +from backend.db_services.dbpermission.constants import RuleActionType +from backend.db_services.dbpermission.db_account.dataclass import AccountRuleMeta +from backend.db_services.dbpermission.db_account.serializers import ModifyAccountRuleSerializer +from backend.db_services.dbpermission.db_authorize.models import DBRuleActionLog +from backend.flow.engine.controller.mysql import MySQLController +from backend.iam_app.dataclass.actions import ActionEnum +from backend.ticket import builders +from backend.ticket.builders.mysql.base import BaseMySQLTicketFlowBuilder +from backend.ticket.constants import FlowRetryType, ItsmApproveMode, TicketType + + +class MySQLAccountRuleChangeSerializer(ModifyAccountRuleSerializer): + last_account_rules = serializers.JSONField(help_text=_("上次账号规则")) + action = serializers.ChoiceField(help_text=_("变更类型(修改/删除)"), choices=RuleActionType.get_choices()) + + +class MySQLAccountRuleChangeFlowParamBuilder(builders.FlowParamBuilder): + delete_controller = MySQLController.mysql_account_rules_delete + change_controller = MySQLController.mysql_account_rules_change + + def format_ticket_data(self): + self.ticket_data.update( + cluster_type=self.ticket_data["account_type"], + operator=self.ticket.creator, + ) + if self.ticket_data["action"] == RuleActionType.CHANGE: + self.ticket_data.update( + id=self.ticket_data["rule_id"], + dbname=self.ticket_data["access_db"], + priv=AccountRuleMeta(privilege=self.ticket_data["privilege"]).privilege, + ) + else: + self.ticket_data.update(id=[self.ticket_data["rule_id"]]) + + def build_controller_info(self) -> dict: + if self.ticket_data["action"] == RuleActionType.CHANGE: + self.controller = self.change_controller + elif self.ticket_data["action"] == RuleActionType.DELETE: + self.controller = self.delete_controller + return super().build_controller_info() + + +class MySQLAccountRuleChangeItsmFlowParamBuilder(builders.ItsmParamBuilder): + def get_params(self): + params = super().get_params() + field_map = {field["key"]: field["value"] for field in params["fields"]} + # 获取权限审批人 + approver_list = DBRuleActionLog.get_notifiers(self.ticket.details["rule_id"]) + field_map["approver"] = ",".join(approver_list) + # 审批模式改成会签 + field_map["approve_mode"] = ItsmApproveMode.CounterSign + # 导出itsm字段 + params["fields"] = [{"key": key, "value": value} for key, value in field_map.items()] + return params + + +@builders.BuilderFactory.register(TicketType.MYSQL_ACCOUNT_RULE_CHANGE, iam=ActionEnum.MYSQL_ADD_ACCOUNT_RULE) +class MySQLAccountRuleChangeFlowBuilder(BaseMySQLTicketFlowBuilder): + serializer = MySQLAccountRuleChangeSerializer + inner_flow_builder = MySQLAccountRuleChangeFlowParamBuilder + itsm_flow_builder = MySQLAccountRuleChangeItsmFlowParamBuilder + retry_type = FlowRetryType.MANUAL_RETRY + + @property + def need_itsm(self): + approver_list = DBRuleActionLog.get_notifiers(self.ticket.details["rule_id"]) + return bool(approver_list) diff --git a/dbm-ui/backend/ticket/builders/tendbcluster/tendb_authorize_rules.py b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_authorize_rules.py index 4435f457a4..cff74e3171 100644 --- a/dbm-ui/backend/ticket/builders/tendbcluster/tendb_authorize_rules.py +++ b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_authorize_rules.py @@ -41,4 +41,4 @@ def need_manual_confirm(self): @builders.BuilderFactory.register(TicketType.TENDBCLUSTER_EXCEL_AUTHORIZE_RULES) class TendbClusterAuthorizeRulesFlowBuilder(TendbClusterAuthorizeRulesFlowBuilder): serializer = MySQLExcelAuthorizeRulesSerializer - inner_flow_name = _("TenDB Cluster 授权执行") + inner_flow_name = _("TenDB Cluster Excel授权执行") diff --git a/dbm-ui/backend/ticket/builders/tendbcluster/tendb_priv_change.py b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_priv_change.py new file mode 100644 index 0000000000..349f5cc713 --- /dev/null +++ b/dbm-ui/backend/ticket/builders/tendbcluster/tendb_priv_change.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +""" +TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-DB管理系统(BlueKing-BK-DBM) available. +Copyright (C) 2017-2023 THL A29 Limited, a Tencent company. All rights reserved. +Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. +You may obtain a copy of the License at https://opensource.org/licenses/MIT +Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on +an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the +specific language governing permissions and limitations under the License. +""" + +from backend.iam_app.dataclass.actions import ActionEnum +from backend.ticket import builders +from backend.ticket.builders.mysql.mysql_priv_change import ( + MySQLAccountRuleChangeFlowParamBuilder, + MySQLAccountRuleChangeItsmFlowParamBuilder, + MySQLAccountRuleChangeSerializer, +) +from backend.ticket.builders.tendbcluster.base import BaseTendbTicketFlowBuilder +from backend.ticket.constants import FlowRetryType, TicketType + + +class TendbAccountRuleChangeSerializer(MySQLAccountRuleChangeSerializer): + pass + + +class TendbAccountRuleChangeFlowParamBuilder(MySQLAccountRuleChangeFlowParamBuilder): + pass + + +class TendbAccountRuleChangeItsmFlowParamBuilder(MySQLAccountRuleChangeItsmFlowParamBuilder): + pass + + +@builders.BuilderFactory.register( + TicketType.TENDBCLUSTER_ACCOUNT_RULE_CHANGE, iam=ActionEnum.TENDBCLUSTER_ADD_ACCOUNT_RULE +) +class TendbAccountRuleChangeFlowBuilder(BaseTendbTicketFlowBuilder): + serializer = TendbAccountRuleChangeSerializer + inner_flow_builder = TendbAccountRuleChangeFlowParamBuilder + itsm_flow_builder = TendbAccountRuleChangeItsmFlowParamBuilder + retry_type = FlowRetryType.MANUAL_RETRY diff --git a/dbm-ui/backend/ticket/constants.py b/dbm-ui/backend/ticket/constants.py index 9308ee4212..040753f7ab 100644 --- a/dbm-ui/backend/ticket/constants.py +++ b/dbm-ui/backend/ticket/constants.py @@ -214,8 +214,9 @@ def get_cluster_type_by_ticket(cls, ticket_type): MYSQL_SLAVE_MIGRATE_UPGRADE = TicketEnumField("MYSQL_SLAVE_MIGRATE_UPGRADE", _("MySQL Slave 迁移升级"), _("版本升级")) MYSQL_RO_SLAVE_UNINSTALL = TicketEnumField("MYSQL_RO_SLAVE_UNINSTALL", _("MySQL非stanby slave下架"), _("集群维护")) MYSQL_PROXY_UPGRADE = TicketEnumField("MYSQL_PROXY_UPGRADE", _("MySQL Proxy升级"), _("版本升级")) - MYSQL_HA_TRANSFER_TO_OTHER_BIZ = TicketEnumField("MYSQL_HA_TRANSFER_TO_OTHER_BIZ", _("TendbHA集群迁移至其他业务"), register_iam=False)# noqa + MYSQL_HA_TRANSFER_TO_OTHER_BIZ = TicketEnumField("MYSQL_HA_TRANSFER_TO_OTHER_BIZ", _("TendbHA集群迁移至其他业务"), register_iam=False) # noqa MYSQL_PUSH_PERIPHERAL_CONFIG = TicketEnumField("MYSQL_PUSH_PERIPHERAL_CONFIG", _("推送周边配置"), register_iam=False) + MYSQL_ACCOUNT_RULE_CHANGE = TicketEnumField("MYSQL_ACCOUNT_RULE_CHANGE", _("MySQL 授权规则变更"), register_iam=False) # SPIDER(TenDB Cluster) TENDBCLUSTER_OPEN_AREA = TicketEnumField("TENDBCLUSTER_OPEN_AREA", _("TenDB Cluster 开区"), _("克隆开区"), register_iam=False) # noqa @@ -229,7 +230,7 @@ def get_cluster_type_by_ticket(cls, ticket_type): TENDBCLUSTER_MASTER_FAIL_OVER = TicketEnumField("TENDBCLUSTER_MASTER_FAIL_OVER", _("TenDB Cluster 主库故障切换"), _("集群维护")) # noqa TENDBCLUSTER_MASTER_SLAVE_SWITCH = TicketEnumField("TENDBCLUSTER_MASTER_SLAVE_SWITCH", _("TenDB Cluster 主从互切"), _("集群维护")) # noqa TENDBCLUSTER_IMPORT_SQLFILE = TicketEnumField("TENDBCLUSTER_IMPORT_SQLFILE", _("TenDB Cluster 变更SQL执行"), _("SQL 任务")) # noqa - TENDBCLUSTER_FORCE_IMPORT_SQLFILE = TicketEnumField("TENDBCLUSTER_FORCE_IMPORT_SQLFILE", _("TenDB Cluster 强制变更SQL执行"), _("SQL 任务"), register_iam=False) # noqa + TENDBCLUSTER_FORCE_IMPORT_SQLFILE = TicketEnumField("TENDBCLUSTER_FORCE_IMPORT_SQLFILE", _("TenDB Cluster 强制变更SQL执行"), register_iam=False) # noqa TENDBCLUSTER_SEMANTIC_CHECK = TicketEnumField("TENDBCLUSTER_SEMANTIC_CHECK", _("TenDB Cluster 模拟执行"), register_iam=False) # noqa TENDBCLUSTER_SPIDER_ADD_NODES = TicketEnumField("TENDBCLUSTER_SPIDER_ADD_NODES", _("TenDB Cluster 扩容接入层"), _("集群维护")) # noqa TENDBCLUSTER_SPIDER_REDUCE_NODES = TicketEnumField("TENDBCLUSTER_SPIDER_REDUCE_NODES", _("TenDB Cluster 缩容接入层"), _("集群维护")) # noqa @@ -245,21 +246,23 @@ def get_cluster_type_by_ticket(cls, ticket_type): TENDBCLUSTER_DISABLE = TicketEnumField("TENDBCLUSTER_DISABLE", _("TenDB Cluster 集群禁用"), register_iam=False) TENDBCLUSTER_DESTROY = TicketEnumField("TENDBCLUSTER_DESTROY", _("TenDB Cluster 集群销毁"), _("集群管理")) TENDBCLUSTER_TEMPORARY_DESTROY = TicketEnumField("TENDBCLUSTER_TEMPORARY_DESTROY", _("TenDB Cluster 临时集群销毁"), _("集群管理")) # noqa - TENDBCLUSTER_NODE_REBALANCE = TicketEnumField("TENDBCLUSTER_NODE_REBALANCE", _("TenDB Cluster 集群容量变更"), _("集群维护")) # noqa + TENDBCLUSTER_NODE_REBALANCE = TicketEnumField("TENDBCLUSTER_NODE_REBALANCE", _("TenDB Cluster 集群容量变更"), _("集群维护")) # noqa TENDBCLUSTER_FULL_BACKUP = TicketEnumField("TENDBCLUSTER_FULL_BACKUP", _("TenDB Cluster 全库备份"), _("备份")) - TENDBCLUSTER_ROLLBACK_CLUSTER = TicketEnumField("TENDBCLUSTER_ROLLBACK_CLUSTER", _("TenDB Cluster 定点构造"), _("回档")) # noqa + TENDBCLUSTER_ROLLBACK_CLUSTER = TicketEnumField("TENDBCLUSTER_ROLLBACK_CLUSTER", _("TenDB Cluster 定点构造"), _("回档")) # noqa TENDBCLUSTER_FLASHBACK = TicketEnumField("TENDBCLUSTER_FLASHBACK", _("TenDB Cluster 闪回"), _("回档")) TENDBCLUSTER_CLIENT_CLONE_RULES = TicketEnumField("TENDBCLUSTER_CLIENT_CLONE_RULES", _("TenDB Cluster 客户端权限克隆"), _("权限管理")) # noqa TENDBCLUSTER_INSTANCE_CLONE_RULES = TicketEnumField("TENDBCLUSTER_INSTANCE_CLONE_RULES", _("TenDB Cluster DB实例权限克隆"), _("权限管理")) # noqa TENDBCLUSTER_AUTHORIZE_RULES = TicketEnumField("TENDBCLUSTER_AUTHORIZE_RULES", _("TenDB Cluster 授权"), _("权限管理")) TENDBCLUSTER_EXCEL_AUTHORIZE_RULES = TicketEnumField("TENDBCLUSTER_EXCEL_AUTHORIZE_RULES", _("TenDB Cluster EXCEL授权"), _("权限管理")) # noqa TENDBCLUSTER_STANDARDIZE = TicketEnumField("TENDBCLUSTER_STANDARDIZE", _("TenDB Cluster 集群标准化"), register_iam=False) - TENDBCLUSTER_METADATA_IMPORT = TicketEnumField("TENDBCLUSTER_METADATA_IMPORT", _("TenDB Cluster 元数据导入"), register_iam=False) # noqa + TENDBCLUSTER_METADATA_IMPORT = TicketEnumField("TENDBCLUSTER_METADATA_IMPORT", _("TenDB Cluster 元数据导入"),register_iam=False) # noqa TENDBCLUSTER_APPEND_DEPLOY_CTL = TicketEnumField("TENDBCLUSTER_APPEND_DEPLOY_CTL", _("TenDB Cluster 追加部署中控"), register_iam=False) # noqa TENDBSINGLE_METADATA_IMPORT = TicketEnumField("TENDBSINGLE_METADATA_IMPORT", _("TenDB Single 元数据导入"), register_iam=False) # noqa TENDBSINGLE_STANDARDIZE = TicketEnumField("TENDBSINGLE_STANDARDIZE", _("TenDB Single 集群标准化"), register_iam=False) # noqa TENDBCLUSTER_DATA_MIGRATE = TicketEnumField("TENDBCLUSTER_DATA_MIGRATE", _("TenDB Cluster DB克隆"), _("数据处理")) TENDBCLUSTER_DUMP_DATA = TicketEnumField("TENDBCLUSTER_DUMP_DATA", _("TenDB Cluster 数据导出"), _("数据处理")) + TENDBCLUSTER_ACCOUNT_RULE_CHANGE = TicketEnumField("TENDBCLUSTER_ACCOUNT_RULE_CHANGE", _("TenDB Cluster 账号规则变更"), register_iam=False) # noqa + # Tbinlogdumper TBINLOGDUMPER_INSTALL = TicketEnumField("TBINLOGDUMPER_INSTALL", _("TBINLOGDUMPER 上架"), register_iam=False) TBINLOGDUMPER_REDUCE_NODES = TicketEnumField("TBINLOGDUMPER_REDUCE_NODES", _("TBINLOGDUMPER 下架"), register_iam=False) # noqa @@ -277,9 +280,9 @@ def get_cluster_type_by_ticket(cls, ticket_type): SQLSERVER_DISABLE = TicketEnumField("SQLSERVER_DISABLE", _("SQLServer 集群禁用"), register_iam=False) SQLSERVER_ENABLE = TicketEnumField("SQLSERVER_ENABLE", _("SQLServer 集群启用"), register_iam=False) SQLSERVER_DBRENAME = TicketEnumField("SQLSERVER_DBRENAME", _("SQLServer DB重命名"), _("集群维护")) - SQLSERVER_MASTER_SLAVE_SWITCH = TicketEnumField("SQLSERVER_MASTER_SLAVE_SWITCH", _("SQLServer 主从互切"), _("集群维护")) # noqa + SQLSERVER_MASTER_SLAVE_SWITCH = TicketEnumField("SQLSERVER_MASTER_SLAVE_SWITCH", _("SQLServer 主从互切"), _("集群维护")) # noqa SQLSERVER_MASTER_FAIL_OVER = TicketEnumField("SQLSERVER_MASTER_FAIL_OVER", _("SQLServer 主库故障切换"), _("集群维护")) - SQLSERVER_RESTORE_LOCAL_SLAVE = TicketEnumField("SQLSERVER_RESTORE_LOCAL_SLAVE", _("SQLServer 原地重建"), _("集群维护")) # noqa + SQLSERVER_RESTORE_LOCAL_SLAVE = TicketEnumField("SQLSERVER_RESTORE_LOCAL_SLAVE", _("SQLServer 原地重建"), _("集群维护")) # noqa SQLSERVER_RESTORE_SLAVE = TicketEnumField("SQLSERVER_RESTORE_SLAVE", _("SQLServer 新机重建"), _("集群维护")) SQLSERVER_ADD_SLAVE = TicketEnumField("SQLSERVER_ADD_SLAVE", _("SQLServer 添加从库"), _("集群维护")) SQLSERVER_RESET = TicketEnumField("SQLSERVER_RESET", _("SQLServer 集群重置"), _("集群维护")) @@ -408,8 +411,10 @@ def get_cluster_type_by_ticket(cls, ticket_type): RIAK_CLUSTER_MIGRATE = TicketEnumField("RIAK_CLUSTER_MIGRATE", _("Riak 集群迁移"), _("集群管理")) # MONGODB - MONGODB_REPLICASET_APPLY = TicketEnumField("MONGODB_REPLICASET_APPLY", _("MongoDB 副本集集群部署"), register_iam=False) # noqa - MONGODB_SHARD_APPLY = TicketEnumField("MONGODB_SHARD_APPLY", _("MongoDB 分片集群部署"), _("集群管理"), register_iam=False) # noqa + MONGODB_REPLICASET_APPLY = TicketEnumField("MONGODB_REPLICASET_APPLY", _("MongoDB 副本集集群部署"), + register_iam=False) # noqa + MONGODB_SHARD_APPLY = TicketEnumField("MONGODB_SHARD_APPLY", _("MongoDB 分片集群部署"), _("集群管理"), + register_iam=False) # noqa MONGODB_EXEC_SCRIPT_APPLY = TicketEnumField("MONGODB_EXEC_SCRIPT_APPLY", _("MongoDB 变更脚本执行"), _("脚本任务")) MONGODB_REMOVE_NS = TicketEnumField("MONGODB_REMOVE_NS", _("MongoDB 清档"), _("数据处理")) MONGODB_FULL_BACKUP = TicketEnumField("MONGODB_FULL_BACKUP", _("MongoDB 全库备份"), _("备份")) @@ -417,7 +422,7 @@ def get_cluster_type_by_ticket(cls, ticket_type): MONGODB_ADD_MONGOS = TicketEnumField("MONGODB_ADD_MONGOS", _("MongoDB 扩容接入层"), _("集群维护")) MONGODB_REDUCE_MONGOS = TicketEnumField("MONGODB_REDUCE_MONGOS", _("MongoDB 缩容接入层"), _("集群维护")) MONGODB_ADD_SHARD_NODES = TicketEnumField("MONGODB_ADD_SHARD_NODES", _("MongoDB 扩容shard节点数"), _("集群维护")) - MONGODB_REDUCE_SHARD_NODES = TicketEnumField("MONGODB_REDUCE_SHARD_NODES", _("MongoDB 缩容shard节点数"), _("集群维护")) + MONGODB_REDUCE_SHARD_NODES = TicketEnumField("MONGODB_REDUCE_SHARD_NODES", _("MongoDB 缩容shard节点数"), _("集群维护")) # noqa MONGODB_SCALE_UPDOWN = TicketEnumField("MONGODB_SCALE_UPDOWN", _("MongoDB 集群容量变更"), _("集群维护")) MONGODB_ENABLE = TicketEnumField("MONGODB_ENABLE", _("MongoDB 集群启用"), register_iam=False) MONGODB_INSTANCE_RELOAD = TicketEnumField("MONGODB_INSTANCE_RELOAD", _("MongoDB 实例重启"), _("集群管理")) @@ -642,6 +647,11 @@ class ItsmTicketNodeEnum(str, StructuredEnum): Remark = EnumField("备注", "备注") +class ItsmApproveMode(int, StructuredEnum): + OrSign = EnumField(0, _("或签模式")) + CounterSign = EnumField(1, _("会签模式")) + + ITSM_FIELD_NAME__ITSM_KEY = { ItsmTicketNodeEnum.ApprovalOption.value: SystemSettingsEnum.ITSM_APPROVAL_KEY, ItsmTicketNodeEnum.Remark.value: SystemSettingsEnum.ITSM_REMARK_KEY,