Compare commits
No commits in common. "e5ce3b2ec90dc012f3e8c825374deb3b4807d777" and "637798b2b923137aa7b1493ba7345bc421441fdc" have entirely different histories.
e5ce3b2ec9
...
637798b2b9
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,9 +1,15 @@
|
||||
build/
|
||||
dist/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
*.spec.bak
|
||||
*.log
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
*.db
|
||||
*.sqlite
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
33
README.md
33
README.md
@ -1,33 +0,0 @@
|
||||
# SANEF Patch Manager v2
|
||||
|
||||
Gestion du patching Linux via Excel + SSH + vSphere (PyQt/Tkinter + PyInstaller).
|
||||
|
||||
## Prérequis
|
||||
- Python 3.11+ sur Windows
|
||||
- Accès réseau proxy SANEF : `http://proxy.sanef.fr:8080`
|
||||
|
||||
## Installation des dépendances
|
||||
```
|
||||
py -m pip install -r requirements.txt --proxy http://proxy.sanef.fr:8080
|
||||
```
|
||||
|
||||
## Exécution depuis les sources
|
||||
```
|
||||
py patch_manager_v2.py
|
||||
```
|
||||
|
||||
## Build de l'exécutable (PyInstaller)
|
||||
```
|
||||
py -m PyInstaller patch_manager_v2.spec --clean --noconfirm
|
||||
```
|
||||
L'exe généré : `dist/SANEF_Patch_Manager.exe`
|
||||
|
||||
## Archive packagée
|
||||
`dist/SANEF_Patch_Manager.7z` contient l'exe prêt à distribuer.
|
||||
|
||||
## Fichiers
|
||||
- `patch_manager_v2.py` — code principal
|
||||
- `patch_manager_v2.spec` — config PyInstaller
|
||||
- `patch_manager_v2_backup_before_theme.py` — backup avant refonte thème
|
||||
- `requirements.txt` — dépendances Python
|
||||
- `build/`, `dist/` — artefacts PyInstaller (inclus dans le repo pour récup rapide)
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
@ -1,66 +0,0 @@
|
||||
|
||||
This file lists modules PyInstaller was not able to find. This does not
|
||||
necessarily mean these modules are required for running your program. Both
|
||||
Python's standard library and 3rd-party Python packages often conditionally
|
||||
import optional modules, some of which may be available only on certain
|
||||
platforms.
|
||||
|
||||
Types of import:
|
||||
* top-level: imported at the top-level - look at these first
|
||||
* conditional: imported within an if-statement
|
||||
* delayed: imported within a function
|
||||
* optional: imported within a try-except-statement
|
||||
|
||||
IMPORTANT: Do NOT post this list to the issue-tracker. Use it as a basis for
|
||||
tracking down the missing module yourself. Thanks!
|
||||
|
||||
missing module named pyimod02_importers - imported by C:\Users\netadmin\AppData\Local\Programs\Python\Python312\Lib\site-packages\PyInstaller\hooks\rthooks\pyi_rth_pkgutil.py (delayed)
|
||||
missing module named urllib.urlopen - imported by urllib (delayed, optional), lxml.html (delayed, optional)
|
||||
missing module named urllib.urlencode - imported by urllib (delayed, optional), lxml.html (delayed, optional)
|
||||
missing module named posix - imported by os (conditional, optional), shutil (conditional), importlib._bootstrap_external (conditional), posixpath (optional)
|
||||
missing module named resource - imported by posix (top-level)
|
||||
missing module named grp - imported by shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional)
|
||||
missing module named pwd - imported by posixpath (delayed, conditional, optional), shutil (delayed, optional), tarfile (optional), pathlib (delayed, optional), subprocess (delayed, conditional, optional), getpass (delayed), netrc (delayed, conditional), http.server (delayed, optional)
|
||||
excluded module named _frozen_importlib - imported by importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named _frozen_importlib_external - imported by importlib._bootstrap (delayed), importlib (optional), importlib.abc (optional), zipimport (top-level)
|
||||
missing module named 'numpy.typing' - imported by PIL._typing (conditional, optional)
|
||||
missing module named _posixsubprocess - imported by subprocess (conditional)
|
||||
missing module named fcntl - imported by subprocess (optional), pty (delayed, optional), invoke.runners (optional), invoke.terminals (conditional), paramiko.agent (delayed)
|
||||
missing module named olefile - imported by PIL.FpxImagePlugin (top-level), PIL.MicImagePlugin (top-level)
|
||||
missing module named defusedxml - imported by openpyxl.xml (delayed, optional), PIL.Image (optional)
|
||||
missing module named OpenSSL - imported by pyVim.sso (top-level)
|
||||
missing module named htmlentitydefs - imported by lxml.html.soupparser (optional)
|
||||
missing module named BeautifulSoup - imported by lxml.html.soupparser (optional)
|
||||
missing module named bs4 - imported by lxml.html.soupparser (optional)
|
||||
missing module named urlparse - imported by pyVmomi.five (conditional), lxml.ElementInclude (optional), lxml.html.html5parser (optional)
|
||||
missing module named urllib2 - imported by lxml.ElementInclude (optional), lxml.html.html5parser (optional)
|
||||
missing module named 'html5lib.treebuilders' - imported by lxml.html.html5parser (top-level)
|
||||
missing module named html5lib - imported by lxml.html._html5builder (top-level), lxml.html.html5parser (top-level)
|
||||
missing module named 'cython.cimports' - imported by lxml.html.diff (optional)
|
||||
missing module named cython - imported by lxml.html.diff (optional), lxml.html._difflib (optional)
|
||||
missing module named lxml_html_clean - imported by lxml.html.clean (optional)
|
||||
excluded module named unittest - imported by doctest (top-level)
|
||||
missing module named termios - imported by getpass (optional), tty (top-level), invoke.runners (optional), invoke.terminals (conditional)
|
||||
missing module named vms_lib - imported by platform (delayed, optional)
|
||||
missing module named 'java.lang' - imported by platform (delayed, optional)
|
||||
missing module named java - imported by platform (delayed)
|
||||
missing module named _winreg - imported by platform (delayed, optional)
|
||||
missing module named readline - imported by cmd (delayed, conditional, optional), code (delayed, conditional, optional), pdb (delayed, optional)
|
||||
missing module named cssselect - imported by lxml.cssselect (optional)
|
||||
missing module named httplib - imported by pyVmomi.five (conditional), pyVmomi.SoapAdapter (conditional)
|
||||
missing module named 'defusedxml.ElementTree' - imported by openpyxl.xml.functions (conditional)
|
||||
missing module named numpy - imported by openpyxl.compat.numbers (optional)
|
||||
missing module named _scproxy - imported by urllib.request (conditional)
|
||||
missing module named pyVmomi.vmodl - imported by pyVmomi (top-level), pyVim.connect (top-level)
|
||||
missing module named Cookie - imported by pyVmomi.SoapAdapter (conditional)
|
||||
missing module named StringIO - imported by pyVmomi.SoapAdapter (conditional)
|
||||
missing module named elementtree - imported by pyVim.connect (optional)
|
||||
missing module named openpyxl.tests - imported by openpyxl.reader.excel (optional)
|
||||
missing module named yaml - imported by invoke.vendor.yaml.cyaml (top-level), invoke.util (optional)
|
||||
missing module named lexicon - imported by invoke.util (optional), invoke.parser.parser (optional), invoke.parser.context (optional)
|
||||
missing module named fluidity - imported by invoke.parser.parser (optional)
|
||||
missing module named 'unittest.mock' - imported by invoke.context (top-level)
|
||||
missing module named sspi - imported by paramiko.ssh_gss (optional)
|
||||
missing module named sspicon - imported by paramiko.ssh_gss (optional)
|
||||
missing module named pywintypes - imported by paramiko.ssh_gss (optional)
|
||||
missing module named gssapi - imported by paramiko.ssh_gss (optional)
|
||||
File diff suppressed because it is too large
Load Diff
BIN
dist/SANEF_Patch_Manager.7z
vendored
BIN
dist/SANEF_Patch_Manager.7z
vendored
Binary file not shown.
BIN
dist/SANEF_Patch_Manager.exe
vendored
BIN
dist/SANEF_Patch_Manager.exe
vendored
Binary file not shown.
@ -670,72 +670,15 @@ def build_fqdn(server, env):
|
||||
return [server, f"{server}.sanef.groupe"]
|
||||
|
||||
def load_key(keyfile):
|
||||
"""Charge une clé SSH privée. Retourne l'objet PKey ou None.
|
||||
Si format PuTTY (.ppk), tente conversion auto via puttygen vers OpenSSH PEM.
|
||||
Stocke le détail de l'échec dans load_key.last_error pour affichage UI."""
|
||||
load_key.last_error = None
|
||||
if not keyfile:
|
||||
if not keyfile or not os.path.exists(keyfile.strip('"').strip("'")):
|
||||
return None
|
||||
keyfile = keyfile.strip('"').strip("'")
|
||||
if not os.path.exists(keyfile):
|
||||
load_key.last_error = f"Fichier clé introuvable : {keyfile}"
|
||||
return None
|
||||
|
||||
# Détection format PuTTY (.ppk)
|
||||
try:
|
||||
with open(keyfile, "r", encoding="utf-8", errors="ignore") as f:
|
||||
first_line = f.readline().strip()
|
||||
except Exception as ex:
|
||||
load_key.last_error = f"Lecture impossible : {ex}"
|
||||
return None
|
||||
|
||||
is_ppk = first_line.startswith("PuTTY-User-Key-File-")
|
||||
if is_ppk:
|
||||
# Paramiko ne lit pas .ppk nativement → tenter conversion auto via puttygen
|
||||
converted = keyfile + ".openssh"
|
||||
puttygen_paths = [
|
||||
r"C:\Program Files\PuTTY\puttygen.exe",
|
||||
r"C:\Program Files (x86)\PuTTY\puttygen.exe",
|
||||
"puttygen", # si dans PATH
|
||||
"puttygen.exe",
|
||||
]
|
||||
import subprocess
|
||||
converted_ok = False
|
||||
for pg in puttygen_paths:
|
||||
try:
|
||||
subprocess.run(
|
||||
[pg, keyfile, "-O", "private-openssh", "-o", converted],
|
||||
check=True, capture_output=True, timeout=10,
|
||||
)
|
||||
converted_ok = True
|
||||
break
|
||||
except (FileNotFoundError, subprocess.SubprocessError, OSError):
|
||||
continue
|
||||
if not converted_ok:
|
||||
load_key.last_error = (
|
||||
f"Format PuTTY (.ppk) détecté pour {os.path.basename(keyfile)}. "
|
||||
f"Paramiko ne lit pas ce format nativement et puttygen.exe est introuvable. "
|
||||
f"Convertir manuellement : "
|
||||
f'puttygen "{keyfile}" -O private-openssh -o "{keyfile}.openssh" '
|
||||
f"puis pointer la conf vers le fichier converti."
|
||||
)
|
||||
return None
|
||||
keyfile = converted
|
||||
|
||||
# Tenter chargement PEM/OpenSSH (multi-format)
|
||||
last_ex = None
|
||||
for cls in [paramiko.RSAKey, paramiko.Ed25519Key, paramiko.ECDSAKey]:
|
||||
try:
|
||||
return cls.from_private_key_file(keyfile)
|
||||
except Exception as ex:
|
||||
last_ex = ex
|
||||
except Exception:
|
||||
continue
|
||||
load_key.last_error = (
|
||||
f"Aucun loader paramiko n'a pu lire {os.path.basename(keyfile)} "
|
||||
f"(RSA / Ed25519 / ECDSA tentés). Dernière erreur : {last_ex}"
|
||||
)
|
||||
return None
|
||||
load_key.last_error = None
|
||||
|
||||
def ssh_connect(server, env, settings, pkey, pkey2, cyb_password=None):
|
||||
"""Connexion SSH avec fallback FQDN et clé. Retourne (client, hostname_effectif)"""
|
||||
@ -2351,28 +2294,8 @@ class PatchManagerV2:
|
||||
self._reload_keys()
|
||||
|
||||
def _reload_keys(self):
|
||||
errors = []
|
||||
kf1 = self.settings.get("keyfile", "")
|
||||
if kf1:
|
||||
self.pkey = load_key(kf1)
|
||||
if self.pkey is None and load_key.last_error:
|
||||
errors.append(f"Clé 1 ({kf1}) :\n{load_key.last_error}")
|
||||
else:
|
||||
self.pkey = None
|
||||
kf2 = self.settings.get("keyfile2", "")
|
||||
if kf2:
|
||||
self.pkey2 = load_key(kf2)
|
||||
if self.pkey2 is None and load_key.last_error:
|
||||
errors.append(f"Clé 2 ({kf2}) :\n{load_key.last_error}")
|
||||
else:
|
||||
self.pkey2 = None
|
||||
if errors:
|
||||
try:
|
||||
messagebox.showwarning("Clés SSH non chargées", "\n\n".join(errors))
|
||||
except Exception:
|
||||
# Pas de UI dispo (lancement CLI ?), juste print
|
||||
for e in errors:
|
||||
print(f"[load_key] {e}")
|
||||
self.pkey = load_key(self.settings.get("keyfile",""))
|
||||
self.pkey2 = load_key(self.settings.get("keyfile2",""))
|
||||
|
||||
# ==========================================================================
|
||||
# ONGLET 1 — LOGIQUE
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
paramiko>=3.0
|
||||
openpyxl>=3.1
|
||||
requests>=2.31
|
||||
pyVmomi>=8.0
|
||||
ttkbootstrap>=1.10
|
||||
pyinstaller>=6.0
|
||||
Loading…
Reference in New Issue
Block a user