Files
DevOps Engineer 08bb0d51b2 feat(provisioning): add Gmail OAuth/App Password guide and helper scripts
Add README.md with decision matrix, step-by-step provisioning flows,
security and compliance checklist, and rollback plan.

Add helper scripts:
- oauth_token_exchange.py: obtains and outputs a refresh token
- gmail_health_check.py: validates API or IMAP connectivity

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-29 21:23:34 +00:00

116 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""
Connectivity health check for Gmail via OAuth (Gmail API) or IMAP (App Password).
Usage:
# OAuth mode
export GMAIL_REFRESH_TOKEN="1//..."
export GMAIL_CLIENT_SECRET_PATH=/path/to/client_secret.json
python3 gmail_health_check.py --mode oauth
# IMAP mode
export GMAIL_APP_PASSWORD="xxxx xxxx xxxx xxxx"
python3 gmail_health_check.py --mode imap --user jonathan@cloudinit.dev
Exit codes:
0 — mailbox reachable
1 — missing/unreadable credentials or bad args
2 — network/authentication error
3 — mailbox reachable but unexpected structure (review needed)
"""
import argparse
import os
import sys
def check_oauth():
refresh_token = os.getenv("GMAIL_REFRESH_TOKEN")
secret_path = os.getenv("GMAIL_CLIENT_SECRET_PATH")
if not refresh_token or not secret_path:
print("Error: GMAIL_REFRESH_TOKEN and GMAIL_CLIENT_SECRET_PATH must be set.", file=sys.stderr)
sys.exit(1)
try:
import google.auth
from google.oauth2.credentials import Credentials
from google.auth.transport.requests import Request
from googleapiclient.discovery import build
except ImportError as e:
print(f"Error: google-api-python-client and google-auth are required ({e}).", file=sys.stderr)
sys.exit(1)
try:
import json
with open(secret_path, "r") as f:
client_config = json.load(f)["installed"]
creds = Credentials(
None,
refresh_token=refresh_token,
token_uri=client_config["token_uri"],
client_id=client_config["client_id"],
client_secret=client_config["client_secret"],
scopes=["https://www.googleapis.com/auth/gmail.readonly"],
)
creds.refresh(Request())
service = build("gmail", "v1", credentials=creds, cache_discovery=False)
profile = service.users().getProfile(userId="me").execute()
labels = service.users().labels().list(userId="me").execute()
except Exception as e:
print(f"Error: Authentication or API call failed ({e}).", file=sys.stderr)
sys.exit(2)
expected_labels = {"INBOX", "SENT"}
found = {lbl["id"] for lbl in labels.get("labels", [])}
missing = expected_labels - found
if missing:
print(f"Warning: Expected labels missing: {missing}", file=sys.stderr)
print(f"Profile: {profile}")
sys.exit(3)
print(f"OK — OAuth connectivity verified. Profile: {profile.get('emailAddress')}")
sys.exit(0)
def check_imap(user: str):
password = os.getenv("GMAIL_APP_PASSWORD")
if not password:
print("Error: GMAIL_APP_PASSWORD must be set for IMAP mode.", file=sys.stderr)
sys.exit(1)
try:
import imaplib
except ImportError:
print("Error: imaplib is part of the standard library; if this fails your Python build is incomplete.", file=sys.stderr)
sys.exit(1)
try:
mail = imaplib.IMAP4_SSL("imap.gmail.com", 993)
mail.login(user, password)
status, _ = mail.select("INBOX")
if status != "OK":
print("Warning: INBOX not selectable. Review folder structure.", file=sys.stderr)
mail.logout()
sys.exit(3)
mail.logout()
except Exception as e:
print(f"Error: IMAP connection or authentication failed ({e}).", file=sys.stderr)
sys.exit(2)
print("OK — IMAP connectivity verified.")
sys.exit(0)
def main():
parser = argparse.ArgumentParser(description="Gmail connectivity health check")
parser.add_argument("--mode", required=True, choices=["oauth", "imap"], help="Which path to test")
parser.add_argument("--user", default="jonathan@cloudinit.dev", help="Gmail address for IMAP mode")
args = parser.parse_args()
if args.mode == "oauth":
check_oauth()
elif args.mode == "imap":
check_imap(args.user)
if __name__ == "__main__":
main()