diff --git a/roles/storage_api/files/requirements.txt b/roles/storage_api/files/requirements.txt index f61f628..6c714da 100644 --- a/roles/storage_api/files/requirements.txt +++ b/roles/storage_api/files/requirements.txt @@ -1,4 +1,6 @@ Flask flask_apscheduler psutil -requests \ No newline at end of file +requests +pywin32 +wmi \ No newline at end of file diff --git a/roles/storage_api/tasks/nssm.yaml b/roles/storage_api/tasks/nssm.yaml index 3f68537..5b3f128 100644 --- a/roles/storage_api/tasks/nssm.yaml +++ b/roles/storage_api/tasks/nssm.yaml @@ -41,7 +41,7 @@ - "failed...... {{ disk_service_status.failed }}" - name: Test API - win_shell: "C:\\Windows\\system32\\curl http://{{ ansible_ssh_host }}:5000/disk" + win_shell: "C:\\Windows\\system32\\curl http://{{ ansible_ssh_host }}:5000/test" register: api_test_output - name: Show Test Results diff --git a/roles/storage_api/tasks/python_venv.yaml b/roles/storage_api/tasks/python_venv.yaml index e531618..858b157 100644 --- a/roles/storage_api/tasks/python_venv.yaml +++ b/roles/storage_api/tasks/python_venv.yaml @@ -6,7 +6,7 @@ state: present - name: Purge venv if asked - when: purge_venv | bool + when: purge_venv | bool and not refresh_api | bool block: - name: Remove service diff --git a/roles/storage_api/templates/disk_service.py b/roles/storage_api/templates/disk_service.py index 01fc8f3..a6daa4e 100644 --- a/roles/storage_api/templates/disk_service.py +++ b/roles/storage_api/templates/disk_service.py @@ -1,3 +1,4 @@ +# this got big... from flask import Flask, jsonify from flask_apscheduler import APScheduler import psutil @@ -6,12 +7,20 @@ import requests from requests import RequestException, Response import json from subprocess import check_output +import win32api +from datetime import datetime, timedelta +import sys +from pathlib import Path +import wmi +import pythoncom +import ctypes +from ctypes import wintypes app = Flask(__name__) scheduler = APScheduler() app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True -# Bits to Bytes etc +# human readable bytes def bytes_to_human_readable(bytes): for unit in ['B', 'KB', 'MB', 'GB', 'TB']: if bytes < 1024.0: @@ -111,8 +120,10 @@ def get_disk_info(): partitions = psutil.disk_partitions() for partition in partitions: usage = psutil.disk_usage(partition.mountpoint) + drive_letter = partition.device.replace('\\\\', '\\').rstrip('\\') disk_info.append({ - 'device': partition.device.replace('\\\\', '\\').rstrip('\\'), + 'device': drive_letter, + 'label': get_drive_label(drive_letter), #'mountpoint': partition.mountpoint, 'fstype': partition.fstype, 'total': bytes_to_human_readable(usage.total), @@ -122,6 +133,101 @@ def get_disk_info(): }) return disk_info +# drive label function +def get_drive_label(drive_letter: str) -> str: + result = "none" + root = drive_letter.strip() + if not root.endswith(':'): + root += ':' + if not root.endswith('\\'): + root += '\\' + + # Make sure the drive actually exists + if not os.path.exists(root): + # Not a valid drive letter, return None so the caller can decide what to do. + print(f"[DEBUG] Drive '{root}' does not exist.") + result = "drive does not exist 0_o" + + # Prepare buffers for the Win32 API call + volume_name_buf = ctypes.create_unicode_buffer(260) # MAX_PATH + fs_name_buf = ctypes.create_unicode_buffer(260) + + serial_number = wintypes.DWORD() + max_component_len = wintypes.DWORD() + file_system_flags = wintypes.DWORD() + + # Call GetVolumeInformationW + res = ctypes.windll.kernel32.GetVolumeInformationW( + ctypes.c_wchar_p(root), # lpRootPathName + volume_name_buf, # lpVolumeNameBuffer + ctypes.sizeof(volume_name_buf), # nVolumeNameSize + ctypes.byref(serial_number), # lpVolumeSerialNumber + ctypes.byref(max_component_len), # lpMaximumComponentLength + ctypes.byref(file_system_flags), # lpFileSystemFlags + fs_name_buf, # lpFileSystemNameBuffer + ctypes.sizeof(fs_name_buf) # nFileSystemNameSize + ) + if res == 0: # The call failed + err = ctypes.get_last_error() + print(f"[ERROR] GetVolumeInformationW failed for '{root}'. " + f"Win32 error code: {err}") + result = "label error" + else: + result = volume_name_buf.value + if volume_name_buf.value == '': + result = "no_label" + + return result + + +# os info function +def get_os_info() -> str: + result = "windows" + pythoncom.CoInitialize() + #try: + wmi_data = wmi.WMI() + os_info = wmi_data.Win32_OperatingSystem()[0] + #return { + # "Name" : os_info.Name, + # "Version" : os_info.Version, + # "BuildNumber": os_info.BuildNumber, + # "InstallDate": os_info.InstallDate, + # "ProductType": int(os_info.ProductType) + #} + # 1. Major version + edition (e.g. "10 Pro") + # os_info.Caption → "Microsoft Windows 10 Pro" + parts = os_info.Caption.split() + major = parts[2] # "10" + edition = parts[3] # "Pro" (for server: "2019", etc.) + major_edition = f"{major} {edition}" + # 2. Build number + build = os_info.BuildNumber + # 3. Install date (WMI gives an ISO‑8601 string) + # e.g. "20210930142300.000000+000" + install_ts = os_info.InstallDate[:14] # "20210930142300" + dt = datetime.strptime(install_ts, "%Y%m%d%H%M%S") + install_date = f"{dt.month}-{dt.day}-{dt.year}" + result = f"Windows {major_edition} - Build {build} - Installed {install_date}" + #except Exception as e: + # print(e) + # result = "wmi_error" + return result + +# server reporter info +def get_server_info() -> dict: + result = {} + drives_dict = get_crystal_disk_info() + data_dict = { + "hostname": "{{ hostname_output.stdout_lines[0] }}", + "os_string": get_os_info(), + "drives": drives_dict["drives"], + "API_KEY": "deadbeef", + "storage_summary": get_disk_info() + + } + result = data_dict + return result + # Flask endpoints @app.route('/disk', methods=['GET']) @@ -132,18 +238,23 @@ def disk(): def drive_health(): return jsonify(get_crystal_disk_info()) +@app.route('/full', methods=['GET']) +def full_summary(): + return jsonify(get_server_info()) + +@app.route('/test', methods=['GET']) +def test_flask(): + return jsonify({ + "message": "hello world", + "os_string": get_os_info() + }) + + def server_reporter(): #base_url="https://cosmostat.matt-cloud.com" base_url="http://10.200.27.20:5001" url = f"{base_url}/storage_client_update" - drives_dict = get_crystal_disk_info() - data_dict = { - "hostname": "{{ hostname_output.stdout_lines[0] }}", - "drives": drives_dict["drives"], - "API_KEY": "deadbeef", - "storage_summary": get_disk_info() - - } + data_dict = get_server_info() result = [] try: response = requests.post(url, json=data_dict) @@ -170,6 +281,13 @@ if __name__ == '__main__': return result # gonna try something wild + + run_date = datetime.now() + timedelta(seconds=15) + scheduler.add_job(id='init_disk_info', + func=update_disk_info, + trigger='date', + run_date=run_date) + scheduler.add_job(id='update_disk_info', func=update_disk_info, trigger='interval', @@ -183,6 +301,6 @@ if __name__ == '__main__': scheduler.init_app(app) scheduler.start() - update_disk_info() + #update_disk_info() app.run(host='0.0.0.0', port={{ api_service_port }})