Skip to content

Commit d3df6e0

Browse files
author
ClaraVnk
committed
Release v1.6.1: code quality improvements, security enhancements, and bug fixes
- Add HTTP timeout protection (30s) to prevent indefinite hangs - Fix relative imports across all modules (from `src.` to `.`) - Fix credential loading to handle tuple return values - Correct type hint `any` to `Any` in config.py - Remove unused imports and variables throughout codebase - Format code with Black (line-length=120) and isort (profile=black) - Pass Bandit security audit with 0 medium/high severity issues
1 parent 2743b68 commit d3df6e0

14 files changed

+125
-246
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,3 +63,4 @@ config/
6363
Makefile
6464
.flake8
6565
.pre-commit-config.yaml
66+
bandit_report.json

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ FROM python:3.11-slim
55
# Métadonnées
66
LABEL maintainer="loutre@ikmail.com"
77
LABEL description="OpenStack Toolbox - Complete Suite with Cron"
8-
LABEL version="1.6.0"
8+
LABEL version="1.6.1"
99

1010
# Variables d'environnement
1111
ENV PYTHONUNBUFFERED=1

README.md

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,19 @@
99

1010
A suite of tools to optimize and manage your OpenStack resources, with multilingual support (FR/EN).
1111

12-
> **Version 1.6.0** - Enhanced security (encrypted SMTP passwords), improved logging with rotation, complete type hints, and better error handling!
12+
> **Version 1.6.1** - Code quality improvements, security enhancements, and better reliability!
1313
14-
## ✨ What's New in v1.6.0
14+
## ✨ What's New in v1.6.1
1515

16+
- 🔒 **HTTP timeout protection** - All HTTP requests now have 30s timeout to prevent indefinite hangs
17+
- 🧹 **Code quality** - Formatted with Black and isort, cleaned unused imports
18+
- 🔧 **Bug fixes** - Fixed relative imports and credential loading issues
19+
-**Security audit** - Passed Bandit security scan with 0 critical issues
20+
- 📝 **Type safety** - Corrected type hints for better IDE support
21+
22+
## 📜 Previous Versions
23+
24+
### v1.6.0
1625
- 🔒 **Encrypted SMTP passwords** - Passwords are now encrypted using Fernet (AES-128)
1726
- 📊 **Professional logging** - Colored console output, automatic rotation, JSON support
1827
- 📝 **Complete type hints** - Better IDE support and code quality
@@ -525,6 +534,26 @@ weekly-notification
525534

526535
## 📝 Changelog
527536

537+
### [1.6.1] - 2024-12-19
538+
539+
**Fixed:**
540+
- 🔧 Fixed relative imports in all modules (from `src.` to `.`)
541+
- 🔧 Fixed credential loading to properly handle tuple return values
542+
- 🔧 Corrected type hint `any` to `Any` in config.py
543+
544+
**Improved:**
545+
- 🔒 Added 30-second timeout to all HTTP requests (prevents hanging)
546+
- 🧹 Removed unused imports and variables
547+
- 🧹 Cleaned whitespace in blank lines
548+
- ✨ Code formatted with Black (line-length=120)
549+
- ✨ Imports sorted with isort (profile=black)
550+
551+
**Security:**
552+
- ✅ Passed Bandit security audit with 0 medium/high severity issues
553+
- 🔒 All HTTP requests now protected against indefinite hangs
554+
555+
**No breaking changes** - Full backward compatibility maintained
556+
528557
### [1.6.0] - 2024-11-21
529558

530559
**Added:**

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "openstack-toolbox"
7-
version = "1.7.0"
7+
version = "1.6.1"
88
description = "A toolbox for OpenStack - Docker deployment with automated monitoring and cron tasks"
99
readme = "README.md"
1010
requires-python = ">=3.9"

src/config.py

Lines changed: 16 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,9 @@
55
import json
66
import os
77
from pathlib import Path
8-
from typing import Dict, Optional, Tuple, List
9-
10-
from dotenv import load_dotenv
11-
from rich import print
12-
from rich.prompt import Prompt
13-
14-
from .exceptions import (
15-
ConfigurationError,
16-
CredentialsError,
17-
SMTPConfigError,
18-
FileOperationError,
19-
)
8+
from typing import Any, Dict, List, Optional, Tuple
9+
10+
from .exceptions import SMTPConfigError
2011
from .security import SecureConfig
2112

2213
CONFIG_DIR = os.path.expanduser("~/.config/openstack-toolbox")
@@ -49,7 +40,7 @@ def get_language_preference() -> str:
4940
with open(CONFIG_FILE, "r", encoding="utf-8") as f:
5041
config = json.load(f)
5142
return config.get("language", "fr")
52-
except (OSError, IOError, json.JSONDecodeError) as e:
43+
except (OSError, IOError, json.JSONDecodeError):
5344
# Log error but return default
5445
pass
5546
return "fr"
@@ -85,7 +76,7 @@ def set_language_preference(lang: str) -> bool:
8576
with open(CONFIG_FILE, "w", encoding="utf-8") as f:
8677
json.dump(config, f, indent=4)
8778
return True
88-
except (OSError, IOError, json.JSONDecodeError) as e:
79+
except (OSError, IOError, json.JSONDecodeError):
8980
return False
9081

9182

@@ -122,16 +113,16 @@ def create_smtp_config_interactive() -> bool:
122113
True
123114
"""
124115
config = configparser.ConfigParser()
125-
116+
126117
server = input("Serveur SMTP [smtp.gmail.com]: ").strip() or "smtp.gmail.com"
127118
port = input("Port SMTP [587]: ").strip() or "587"
128119
username = input("Utilisateur SMTP: ").strip()
129120
password = getpass.getpass("Mot de passe SMTP: ").strip()
130-
121+
131122
# Encrypt password for security
132123
secure = SecureConfig(Path(CONFIG_DIR))
133124
encrypted_password = secure.encrypt(password)
134-
125+
135126
config["SMTP"] = {
136127
"server": server,
137128
"port": port,
@@ -142,7 +133,7 @@ def create_smtp_config_interactive() -> bool:
142133

143134
from_email = input(f"Email expéditeur [{username}]: ").strip() or username
144135
to_email = input("Email destinataire: ").strip()
145-
136+
146137
config["Email"] = {
147138
"from": from_email,
148139
"to": to_email,
@@ -158,7 +149,7 @@ def create_smtp_config_interactive() -> bool:
158149
raise SMTPConfigError(f"Failed to save SMTP configuration: {e}") from e
159150

160151

161-
def load_smtp_config() -> Optional[Dict[str, any]]:
152+
def load_smtp_config() -> Optional[Dict[str, Any]]:
162153
"""
163154
Charge la configuration SMTP depuis le fichier de configuration.
164155
@@ -192,7 +183,7 @@ def load_smtp_config() -> Optional[Dict[str, any]]:
192183

193184
password = config["SMTP"]["password"]
194185
is_encrypted = config["SMTP"].get("encrypted", "false") == "true"
195-
186+
196187
# Decrypt password if it's encrypted
197188
if is_encrypted:
198189
secure = SecureConfig(Path(CONFIG_DIR))
@@ -207,27 +198,23 @@ def load_smtp_config() -> Optional[Dict[str, any]]:
207198
"to_email": config["Email"]["to"],
208199
}
209200
except (KeyError, ValueError, configparser.Error) as e:
210-
raise SMTPConfigError(
211-
f"Invalid SMTP configuration file: {e}"
212-
) from e
201+
raise SMTPConfigError(f"Invalid SMTP configuration file: {e}") from e
213202
except Exception as e:
214-
raise SMTPConfigError(
215-
f"Failed to load SMTP configuration: {e}"
216-
) from e
203+
raise SMTPConfigError(f"Failed to load SMTP configuration: {e}") from e
217204

218205

219206
def load_openstack_credentials() -> Tuple[Optional[Dict[str, str]], List[str]]:
220207
"""
221208
Charge les identifiants OpenStack depuis les variables d'environnement.
222-
209+
223210
Returns:
224211
Tuple contenant:
225212
- Dict des credentials si toutes les variables sont présentes, None sinon
226213
- Liste des variables manquantes
227-
214+
228215
Raises:
229216
CredentialsError: Si les variables critiques sont manquantes
230-
217+
231218
Examples:
232219
>>> creds, missing = load_openstack_credentials()
233220
>>> if creds:

src/logger.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,11 @@ def setup_logger(
8383
log_path = Path(log_file)
8484
log_path.parent.mkdir(parents=True, exist_ok=True)
8585

86-
file_handler = RotatingFileHandler(
87-
log_file, maxBytes=max_bytes, backupCount=backup_count
88-
)
86+
file_handler = RotatingFileHandler(log_file, maxBytes=max_bytes, backupCount=backup_count)
8987
file_handler.setLevel(logging.DEBUG) # Always log everything to file
9088

9189
if json_format:
92-
json_formatter = jsonlogger.JsonFormatter(
93-
"%(asctime)s %(name)s %(levelname)s %(message)s"
94-
)
90+
json_formatter = jsonlogger.JsonFormatter("%(asctime)s %(name)s %(levelname)s %(message)s")
9591
file_handler.setFormatter(json_formatter)
9692
else:
9793
file_formatter = logging.Formatter(

src/openstack_admin.py

Lines changed: 12 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
from rich.table import Table
1212
from rich.tree import Tree
1313

14-
from src.config import get_language_preference, load_openstack_credentials
15-
from src.utils import format_size, print_header
14+
from .config import get_language_preference, load_openstack_credentials
15+
from .utils import format_size, print_header
1616

1717
# Dictionnaire des traductions
1818
TRANSLATIONS = {
@@ -128,16 +128,10 @@ def get_project_details(conn, project_id):
128128
print(TRANSLATIONS[lang]["project_name"].format(project.name))
129129
print(TRANSLATIONS[lang]["project_description"].format(project.description))
130130
print(TRANSLATIONS[lang]["project_domain"].format(project.domain_id))
131-
is_active = (
132-
TRANSLATIONS[lang]["yes"]
133-
if project.is_enabled
134-
else TRANSLATIONS[lang]["no"]
135-
)
131+
is_active = TRANSLATIONS[lang]["yes"] if project.is_enabled else TRANSLATIONS[lang]["no"]
136132
print(TRANSLATIONS[lang]["project_active"].format(is_active))
137133
else:
138-
print(
139-
f"[bold red]{TRANSLATIONS[lang]['no_project'].format(project_id)}[/bold red]"
140-
)
134+
print(f"[bold red]{TRANSLATIONS[lang]['no_project'].format(project_id)}[/bold red]")
141135

142136

143137
def list_images(conn):
@@ -236,14 +230,8 @@ def list_volumes(conn):
236230
table.add_column("Attaché", style="blue")
237231
table.add_column("Snapshot associé", style="magenta")
238232
for volume in volumes:
239-
attached = (
240-
TRANSLATIONS[lang]["yes"]
241-
if volume.attachments
242-
else TRANSLATIONS[lang]["no"]
243-
)
244-
snapshot_id = (
245-
volume.snapshot_id if volume.snapshot_id else TRANSLATIONS[lang]["none"]
246-
)
233+
attached = TRANSLATIONS[lang]["yes"] if volume.attachments else TRANSLATIONS[lang]["no"]
234+
snapshot_id = volume.snapshot_id if volume.snapshot_id else TRANSLATIONS[lang]["none"]
247235
table.add_row(
248236
volume.id,
249237
volume.name,
@@ -273,9 +261,7 @@ def mounted_volumes(conn):
273261
instance_id = instance.id
274262
instance_name = instance.name
275263
if instance_id in instance_volumes:
276-
tree[instance_name] = [
277-
volume.name for volume in instance_volumes[instance_id]
278-
]
264+
tree[instance_name] = [volume.name for volume in instance_volumes[instance_id]]
279265
else:
280266
tree[instance_name] = []
281267

@@ -373,21 +359,15 @@ def process_resource_parallel(resource_type, resource, conn):
373359
"details": f"Size: {size}, Status: {resource.status}",
374360
}
375361
elif resource_type == "image":
376-
size = (
377-
format_size(resource.size)
378-
if resource.size
379-
else TRANSLATIONS[lang]["unknown"]
380-
)
362+
size = format_size(resource.size) if resource.size else TRANSLATIONS[lang]["unknown"]
381363
return {
382364
"id": resource.id,
383365
"name": resource.name,
384366
"type": "Image",
385367
"details": f"Size: {size}, Status: {resource.status}",
386368
}
387369
except Exception as e:
388-
print(
389-
f"[red]Erreur lors du traitement de la ressource {resource.id}: {str(e)}[/red]"
390-
)
370+
print(f"[red]Erreur lors du traitement de la ressource {resource.id}: {str(e)}[/red]")
391371
return None
392372

393373

@@ -445,9 +425,7 @@ def list_all_resources(conn):
445425
if result:
446426
processed_resources.append(result)
447427
except Exception as e:
448-
print(
449-
f"[red]Erreur lors du traitement d'une ressource : {str(e)}[/red]"
450-
)
428+
print(f"[red]Erreur lors du traitement d'une ressource : {str(e)}[/red]")
451429

452430
# Affichage des résultats
453431
table = Table(title="")
@@ -457,9 +435,7 @@ def list_all_resources(conn):
457435
table.add_column(TRANSLATIONS[lang]["details_column"], style="white")
458436

459437
for resource in sorted(processed_resources, key=lambda x: (x["type"], x["name"])):
460-
table.add_row(
461-
resource["type"], resource["id"], resource["name"], resource["details"]
462-
)
438+
table.add_row(resource["type"], resource["id"], resource["name"], resource["details"])
463439

464440
console = Console()
465441
console.print(table)
@@ -488,7 +464,7 @@ def main():
488464
print(header)
489465

490466
# Test des credentials
491-
creds = load_openstack_credentials()
467+
creds, missing_vars = load_openstack_credentials()
492468
if not creds:
493469
print(f"[bold red]{TRANSLATIONS[lang]['connection_error']}[/bold red]")
494470
return

0 commit comments

Comments
 (0)