changement vers google drive storage pour les contrats.
CI - Security, Lint & Tests / validate (push) Failing after 1m15s
CI - Security, Lint & Tests / validate (push) Failing after 1m15s
Tentative d'obtenir un token de connection échoue
This commit is contained in:
@@ -42,7 +42,11 @@ from urllib.parse import unquote, urlparse
|
||||
# to be — see DOCUMENTS_DIR below.
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
||||
|
||||
from app.storage import backups_root, documents_root # noqa: E402 — needs the path above
|
||||
from app.storage import ( # noqa: E402 — needs the path above
|
||||
backups_root,
|
||||
document_storage_backend,
|
||||
documents_root,
|
||||
)
|
||||
|
||||
# Configuration
|
||||
BACKUP_DIR = backups_root()
|
||||
@@ -292,6 +296,75 @@ def backup_documents():
|
||||
return zip_path
|
||||
|
||||
|
||||
def backup_drive_documents():
|
||||
"""Download every file from the configured Google Drive folder and archive it.
|
||||
|
||||
Called **in addition to** ``backup_documents()`` when the storage backend is
|
||||
``google_drive``. When the backend is ``local`` this function silently
|
||||
skips — local backups already handled by ``backup_documents()``.
|
||||
|
||||
Returns:
|
||||
str | None: Path to the created archive, or None when skipped.
|
||||
|
||||
Raises:
|
||||
BackupError: If Drive is configured but listing or downloading fails.
|
||||
"""
|
||||
if document_storage_backend() != 'google_drive':
|
||||
print('[SKIP] Google Drive is not configured; local documents only.')
|
||||
return None
|
||||
|
||||
try:
|
||||
from app.google_drive import (
|
||||
GoogleDriveStorageError,
|
||||
list_folder_files,
|
||||
)
|
||||
from app.google_drive import (
|
||||
download_file as download_drive_file,
|
||||
)
|
||||
except ImportError:
|
||||
raise BackupError(
|
||||
'Google Drive dependencies are not installed. Install requirements.txt again.'
|
||||
) from None
|
||||
|
||||
print('[INFO] Listing Google Drive contract folder...')
|
||||
try:
|
||||
files = list_folder_files()
|
||||
except (GoogleDriveStorageError, Exception) as exc: # noqa: BLE001 — boundary
|
||||
raise BackupError(f'Google Drive listing failed: {exc}') from exc
|
||||
|
||||
if not files:
|
||||
print('[INFO] Google Drive folder is empty; no Drive documents to back up.')
|
||||
return None
|
||||
|
||||
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
|
||||
archive_basename = os.path.join(BACKUP_DIR, f'documents_backup_drive_{timestamp}')
|
||||
|
||||
download = {}
|
||||
for file_id, name in files:
|
||||
print(f' Downloading {name} ({file_id})...')
|
||||
try:
|
||||
download[name] = download_drive_file(file_id)
|
||||
except (GoogleDriveStorageError, Exception) as exc: # noqa: BLE001 — boundary
|
||||
raise BackupError(f'Google Drive download failed for {name}: {exc}') from exc
|
||||
|
||||
try:
|
||||
import zipfile
|
||||
|
||||
zip_path = f'{archive_basename}.zip'
|
||||
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zf:
|
||||
for name, data in download.items():
|
||||
zf.writestr(name, data)
|
||||
except Exception as exc: # noqa: BLE001 — boundary
|
||||
raise BackupError(f'Drive document archive failed: {exc}') from exc
|
||||
|
||||
if not os.path.exists(zip_path) or os.path.getsize(zip_path) == 0:
|
||||
raise BackupError('Drive document archiver reported success but produced an empty file.')
|
||||
|
||||
size_mb = os.path.getsize(zip_path) / (1024 * 1024)
|
||||
print(f'[OK] Drive documents backed up to: {zip_path} ({size_mb:.1f} MB)')
|
||||
return zip_path
|
||||
|
||||
|
||||
def cleanup_old_backups():
|
||||
"""Remove backup files older than BACKUP_RETENTION_DAYS."""
|
||||
if not os.path.exists(BACKUP_DIR):
|
||||
@@ -365,10 +438,16 @@ def main(argv=None):
|
||||
except BackupError as exc:
|
||||
documents_ok = False
|
||||
print(f'[ERROR] {exc}')
|
||||
drive_ok = True
|
||||
try:
|
||||
backup_drive_documents()
|
||||
except BackupError as exc:
|
||||
drive_ok = False
|
||||
print(f'[ERROR] {exc}')
|
||||
cleanup_old_backups()
|
||||
|
||||
print()
|
||||
if verified and documents_ok:
|
||||
if verified and documents_ok and drive_ok:
|
||||
print('=== Backup completed successfully ===')
|
||||
return 0
|
||||
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
"""Obtain a Google Drive refresh token for the application.
|
||||
|
||||
Run this once from a development machine that has a browser. It opens a local
|
||||
OAuth redirect server, asks you to log in, and prints the refresh token.
|
||||
|
||||
Usage:
|
||||
python app/supporting_scripts/get_refresh_token.py
|
||||
|
||||
Requires:
|
||||
pip install google-auth-oauthlib
|
||||
|
||||
Before running:
|
||||
1. Create a Google Cloud project and enable the Drive API.
|
||||
2. Create a **Desktop** OAuth 2.0 client ID.
|
||||
3. Add your email as a test user in the OAuth consent screen.
|
||||
4. Run this script and paste the values when prompted.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
def _prompt(label, env_name, default=""):
|
||||
"""Ask the user for a value, accepting an env-var fallback or a default."""
|
||||
candidate = os.getenv(env_name) or default
|
||||
hint = f" [{candidate}]" if candidate else ""
|
||||
raw = input(f"{label}{hint}: ").strip()
|
||||
if raw:
|
||||
return raw
|
||||
if candidate:
|
||||
return candidate
|
||||
return default
|
||||
|
||||
|
||||
def _validate_client_id(client_id):
|
||||
"""Warn if the client ID doesn't look like a real OAuth client ID."""
|
||||
if client_id == "YOUR_CLIENT_ID.apps.googleusercontent.com":
|
||||
print("[ERROR] You must provide a real client ID from the Google Cloud console.")
|
||||
return False
|
||||
if not client_id.endswith(".apps.googleusercontent.com"):
|
||||
print(
|
||||
f"[WARNING] Client ID should end with '.apps.googleusercontent.com'.\n"
|
||||
f" Got: {client_id!r}\n"
|
||||
f" If this is a placeholder, the OAuth request will fail.\n"
|
||||
)
|
||||
return True
|
||||
|
||||
|
||||
def _validate_client_secret(secret):
|
||||
if secret == "YOUR_CLIENT_SECRET":
|
||||
print("[ERROR] You must provide a real client secret from the Google Cloud console.")
|
||||
return False
|
||||
# The GOCSPX- prefix is now used by every client type, so it no longer
|
||||
# tells us anything about whether the client was created as a Desktop app.
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
print("=" * 60)
|
||||
print("Google Drive Refresh Token — Setup")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# When both env vars are set, skip prompts entirely for headless runs.
|
||||
env_id = os.getenv("GOOGLE_DRIVE_CLIENT_ID")
|
||||
env_secret = os.getenv("GOOGLE_DRIVE_CLIENT_SECRET")
|
||||
if env_id and env_secret:
|
||||
client_id = env_id
|
||||
client_secret = env_secret
|
||||
print(f"Using Client ID from environment: {client_id[:30]}...")
|
||||
else:
|
||||
print("Enter your Google Cloud OAuth credentials.")
|
||||
print("You can also set GOOGLE_DRIVE_CLIENT_ID and GOOGLE_DRIVE_CLIENT_SECRET")
|
||||
print("environment variables and re-run to skip the prompts.")
|
||||
print()
|
||||
|
||||
client_id = _prompt("Client ID", "GOOGLE_DRIVE_CLIENT_ID")
|
||||
client_secret = _prompt("Client Secret", "GOOGLE_DRIVE_CLIENT_SECRET")
|
||||
|
||||
if not _validate_client_id(client_id) or not _validate_client_secret(client_secret):
|
||||
sys.exit(1)
|
||||
|
||||
print()
|
||||
print("Opening browser to authenticate with Google...")
|
||||
print("(If it doesn't open, copy the URL below into your browser.)")
|
||||
print()
|
||||
|
||||
from google_auth_oauthlib.flow import InstalledAppFlow
|
||||
|
||||
SCOPE = "https://www.googleapis.com/auth/drive.file"
|
||||
|
||||
client_config = {
|
||||
"installed": {
|
||||
"client_id": client_id,
|
||||
"client_secret": client_secret,
|
||||
"redirect_uris": ["http://localhost"],
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://oauth2.googleapis.com/token",
|
||||
}
|
||||
}
|
||||
|
||||
flow = InstalledAppFlow.from_client_config(client_config, scopes=[SCOPE])
|
||||
credentials = flow.run_local_server(host="localhost", port=8080, open_browser=True)
|
||||
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("SUCCESS — copy these values into your .env file:")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("DOCUMENT_STORAGE_BACKEND=google_drive")
|
||||
print(f"GOOGLE_DRIVE_CLIENT_ID={credentials.client_id}")
|
||||
print(f"GOOGLE_DRIVE_CLIENT_SECRET={client_secret}")
|
||||
print(f"GOOGLE_DRIVE_REFRESH_TOKEN={credentials.refresh_token}")
|
||||
print("GOOGLE_DRIVE_FOLDER_ID=<get this from the Drive folder URL>")
|
||||
print()
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user