跳到主內容

GLPI大量匯入 States & entities

  BUBU 因客戶想要從 GLPI 上面設定每個設備狀態,有請 AI 協助生成一支小程式來協助匯入狀態的欄值資訊,如果有使用到 entities 實體區分的話,每個實體區分都要設定到才能選擇到。

測試過程


  程式是用 python 去執行,因此有需要安裝必要的套件才能正常使用

安裝相關套件

  • 安裝必要套件
apt install -y libmariadb-dev python3-venv python3-dev -y
  • 建議 python 虛擬環境
python3 -m venv ./venv
  • 進入到虛擬環境
source ./venv/bin/activate
  • 安裝 mariadb 套件
pip3 install mariadb
程式執行

  • 首先要先建立個狀態清單是 .csv 檔 vim stateslist.csv,範例如下
使用中
備品
  • 建立要執行的程式名稱 vim glpi_status.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 導入必要的模組
import mariadb  # 用於連接 MariaDB 資料庫
import csv      # 用於處理 CSV 檔案
import sys      # 用於系統相關操作,如退出程式
import os       # 用於檔案和目錄操作
import logging  # 用於記錄日誌
from datetime import datetime  # 用於處理日期和時間

# 固定的 CSV 檔案路徑 - 您可以修改這個路徑指向您的狀態清單檔案
CSV_FILE_PATH = "states.csv"  

# 資料庫連線設定 - 請修改為您的資料庫連線資訊
DB_CONFIG = {
    'user': 'root',         # 資料庫使用者名稱
    'password': 'your_password',  # 資料庫密碼
    'host': 'localhost',    # 資料庫主機地址
    'port': 3306,           # 資料庫連接埠
    'database': 'glpi'      # 資料庫名稱
}

# 日誌設定
LOG_DIR = "logs"            # 日誌檔案存放目錄
LOG_PREFIX = "glpi_states_import"  # 日誌檔案名稱前綴

def setup_logging():
    """設定日誌記錄系統"""
    # 確保日誌目錄存在,如果不存在則建立
    if not os.path.exists(LOG_DIR):
        os.makedirs(LOG_DIR)
    
    # 建立包含日期時間的日誌檔名,格式為 LOG_PREFIX_年月日_時分秒.log
    current_time = datetime.now().strftime('%Y%m%d_%H%M%S')
    log_file = os.path.join(LOG_DIR, f"{LOG_PREFIX}_{current_time}.log")
    
    # 設定日誌格式和處理器
    logging.basicConfig(
        level=logging.INFO,  # 設定日誌級別為 INFO
        format='%(asctime)s - %(levelname)s - %(message)s',  # 設定日誌格式
        handlers=[
            logging.FileHandler(log_file, encoding='utf-8'),  # 將日誌寫入檔案
            logging.StreamHandler()  # 同時在控制台顯示日誌
        ]
    )
    
    logger = logging.getLogger("GLPI_States_Import")  # 建立日誌記錄器
    logger.info(f"日誌檔案位置: {log_file}")  # 記錄日誌檔案位置
    return logger

# 建立日誌記錄器
logger = setup_logging()

def connect_to_db():
    """連接到 MariaDB 資料庫"""
    try:
        # 嘗試連接到資料庫
        conn = mariadb.connect(**DB_CONFIG)
        conn.autocommit = False  # 關閉自動提交,使用手動事務管理
        logger.info("成功連接到資料庫")
        return conn
    except mariadb.Error as e:
        # 如果連接失敗,記錄錯誤並退出程式
        logger.error(f"無法連接到資料庫: {e}")
        sys.exit(1)

def get_all_entities(conn):
    """從資料庫取得所有實體資訊"""
    try:
        # 建立游標以執行SQL查詢
        cursor = conn.cursor(dictionary=True)  # 使用字典形式返回結果
        # 執行SQL查詢,選取所有實體的id、name和completename
        cursor.execute("SELECT id, name, completename FROM glpi_entities")
        # 取得所有查詢結果
        entities = cursor.fetchall()
        cursor.close()
        logger.info(f"成功取得 {len(entities)} 個實體資訊")
        return entities
    except mariadb.Error as e:
        # 如果查詢失敗,記錄錯誤
        logger.error(f"取得實體資訊時發生錯誤: {e}")
        return []

def read_states_from_csv(csv_file):
    """從 CSV 檔案讀取狀態清單"""
    states = []  # 用於儲存讀取到的狀態
    try:
        # 打開CSV檔案並讀取
        with open(csv_file, 'r', encoding='utf-8') as f:
            csv_reader = csv.reader(f)
            for row in csv_reader:
                if row and len(row) > 0:
                    # 假設 CSV 只有一欄,每列是一個狀態名稱
                    state_name = row[0].strip()  # 去除前後空白
                    if state_name and not state_name.startswith('#'):  # 忽略註解行(以#開頭)
                        states.append(state_name)
        logger.info(f"從 CSV 檔案成功讀取 {len(states)} 個狀態")
    except Exception as e:
        # 如果讀取失敗,記錄錯誤
        logger.error(f"讀取 CSV 檔案時發生錯誤: {e}")
    
    return states

def state_exists(conn, state_name, entity_id):
    """檢查指定實體中是否已存在此狀態"""
    cursor = conn.cursor()
    # 執行查詢,檢查指定實體是否已有此狀態
    cursor.execute(
        "SELECT id FROM glpi_states WHERE name = ? AND entities_id = ?",
        (state_name, entity_id)
    )
    result = cursor.fetchone()  # 取得查詢結果
    cursor.close()
    return result is not None  # 如果找到記錄,返回 True,否則返回 False

def insert_state(conn, state_name, entity, comment=""):
    """在指定實體中插入新狀態"""
    # 檢查狀態是否已存在
    if state_exists(conn, state_name, entity['id']):
        logger.info(f"狀態 '{state_name}' 已存在於實體 '{entity['name']}',跳過...")
        return False
    
    try:
        # 取得當前時間,用於設定記錄的建立時間
        current_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        # 設定 completename 等於 name 的值
        completename = state_name
        
        # 使用實體ID作為states_id,確保每個實體的狀態組合是唯一的
        # 這樣可以避免唯一鍵衝突,因為表中有 UNIQUE KEY `unicity` (`states_id`,`name`)
        states_id = entity['id']
        
        # 建立游標以執行SQL插入
        cursor = conn.cursor()
        # 執行SQL插入,新增狀態記錄
        cursor.execute("""
            INSERT INTO glpi_states (
                name, entities_id, is_recursive, comment, states_id,
                completename, level, ancestors_cache, sons_cache,
                is_visible_computer, is_visible_monitor, is_visible_networkequipment,
                is_visible_peripheral, is_visible_phone, is_visible_printer,
                is_visible_softwareversion, is_visible_softwarelicense, is_visible_line,
                is_visible_certificate, is_visible_rack, is_visible_passivedcequipment,
                is_visible_enclosure, is_visible_pdu, is_visible_cluster,
                is_visible_contract, is_visible_appliance, is_visible_databaseinstance,
                is_visible_cable, is_visible_unmanaged, date_mod, date_creation
            ) VALUES (
                ?, ?, 0, ?, ?,
                ?, 1, '[]', '{}',
                1, 1, 1,
                1, 1, 1,
                1, 1, 1,
                1, 1, 1,
                1, 1, 1,
                1, 1, 1,
                1, 1, ?, ?
            )
        """, (
            state_name, entity['id'], comment, states_id,
            completename, current_time, current_time
        ))
        
        # 取得新插入記錄的ID
        state_id = cursor.lastrowid
        
        # 更新 sons_cache JSON字段
        sons_cache = '{"' + str(state_id) + '":' + str(state_id) + '}'
        cursor.execute(
            "UPDATE glpi_states SET sons_cache = ? WHERE id = ?",
            (sons_cache, state_id)
        )
        
        cursor.close()
        # 記錄成功新增狀態
        logger.info(f"已建立狀態: '{state_name}' 於實體 '{entity['name']}' (ID: {state_id}, states_id: {states_id})")
        return True
    except mariadb.Error as e:
        # 如果插入失敗,記錄錯誤
        logger.error(f"插入狀態時發生錯誤: {e}")
        return False

def import_states(conn, states, entities):
    """為所有實體匯入狀態清單"""
    total_added = 0  # 記錄新增的狀態數量
    
    # 遍歷所有實體
    for entity in entities:
        logger.info(f"處理實體: {entity['name']} (ID: {entity['id']})")
        
        # 為每個實體新增所有狀態
        for state_name in states:
            comment = f"{state_name} - 由匯入程式建立"  # 設定狀態的說明文字
            if insert_state(conn, state_name, entity, comment):
                total_added += 1  # 如果成功新增,計數加1
    
    return total_added

def main():
    """主程式入口點"""
    logger.info("=== GLPI 狀態匯入程式開始執行 ===")
    
    # 使用固定的 CSV 檔案路徑
    csv_file = CSV_FILE_PATH
    # 檢查檔案是否存在
    if not os.path.exists(csv_file):
        logger.error(f"錯誤: 找不到檔案 {csv_file}")
        return
    
    # 讀取狀態清單
    states = read_states_from_csv(csv_file)
    if not states:
        logger.error("錯誤: 無法從 CSV 檔案讀取狀態,或檔案為空")
        return
    
    logger.info(f"已讀取 {len(states)} 個狀態: {', '.join(states)}")
    
    # 連接資料庫
    conn = connect_to_db()
    
    try:
        # 取得所有實體
        entities = get_all_entities(conn)
        if not entities:
            logger.error("錯誤: 無法取得實體資訊")
            return
        
        logger.info(f"找到 {len(entities)} 個實體")
        
        # 開始匯入,不再詢問確認
        logger.info(f"開始匯入狀態,將為 {len(entities)} 個實體各建立 {len(states)} 個狀態...")
        total_added = import_states(conn, states, entities)
        
        # 提交交易,將所有變更保存到資料庫
        conn.commit()
        logger.info(f"匯入完成! 共新增 {total_added} 筆狀態資料")
        
    except Exception as e:
        # 如果發生異常,回滾所有變更
        conn.rollback()
        logger.error(f"發生錯誤: {e}")
    finally:
        # 無論成功或失敗,都關閉資料庫連接
        conn.close()
        logger.info("=== GLPI 狀態匯入程式執行結束 ===")

# 程式入口點,確保只有直接執行此檔案時才會執行main函數
if __name__ == "__main__":
    main()

補充說明


  • 該程式的主要功能包括:
  1. 設定日誌系統:
    • 建立帶有時間戳記的日誌檔案
    • 將日誌同時輸出到檔案和控制台
  2. 資料庫連接:
    • 連接到 MariaDB 資料庫
    • 使用交易管理確保資料一致性
  3. 讀取 CSV 檔案:
    • 從指定的 CSV 檔案讀取狀態清單
    • 支援忽略註解行(以 # 開頭)
  4. 處理實體資料:
    • 從資料庫取得所有實體資訊
    • 為每個實體建立指定的狀態
  5. 狀態插入邏輯:
    • 檢查狀態是否已存在,避免重複
    • 使用實體 ID 作為 states_id,解決唯一鍵衝突問題
    • 更新狀態記錄的其他相關欄位
  • 程式使用 states_id = entity['id'] 的方式來確保每個實體的狀態組合是唯一的,這樣可以避免 UNIQUE KEY unicity (states_id, name) 帶來的唯一鍵衝突。

  • 這個程式可以直接使用,只需要修改以下幾個設定:

    1. CSV_FILE_PATH - 設定您的狀態清單 CSV 檔案路徑
    2. DB_CONFIG - 設定您的資料庫連線資訊
    3. LOG_DIR - 設定日誌檔案的儲存目錄 執行後,程式會自動為所有實體建立指定的狀態,並生成詳細的日誌記錄。

備註





參考相關網頁