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()
補充說明
- 該程式的主要功能包括:
- 設定日誌系統:
- 建立帶有時間戳記的日誌檔案
- 將日誌同時輸出到檔案和控制台
- 資料庫連接:
- 連接到 MariaDB 資料庫
- 使用交易管理確保資料一致性
- 讀取 CSV 檔案:
- 從指定的 CSV 檔案讀取狀態清單
- 支援忽略註解行(以 # 開頭)
- 處理實體資料:
- 從資料庫取得所有實體資訊
- 為每個實體建立指定的狀態
- 狀態插入邏輯:
- 檢查狀態是否已存在,避免重複
- 使用實體 ID 作為 states_id,解決唯一鍵衝突問題
- 更新狀態記錄的其他相關欄位
-
程式使用
states_id = entity['id']
的方式來確保每個實體的狀態組合是唯一的,這樣可以避免UNIQUE KEY unicity (states_id, name)
帶來的唯一鍵衝突。 -
這個程式可以直接使用,只需要修改以下幾個設定:
- CSV_FILE_PATH - 設定您的狀態清單 CSV 檔案路徑
- DB_CONFIG - 設定您的資料庫連線資訊
- LOG_DIR - 設定日誌檔案的儲存目錄 執行後,程式會自動為所有實體建立指定的狀態,並生成詳細的日誌記錄。