#!/usr/bin/python3 import urllib.request, urllib.error import re import sys import yaml repos = { "https://github.com/OCA/account-financial-reporting": [ "account_tax_balance" ], "https://github.com/OCA/account-financial-tools": [ "account_lock_date_update", "account_move_name_sequence", "account_reconcile_show_boolean" ], "https://github.com/OCA/account-invoicing": [ "sale_timesheet_invoice_description" ], "https://github.com/OCA/account-reconcile": [ "account_reconciliation_widget" ], "https://github.com/OCA/bank-statement-import": [ "account_statement_import", "account_statement_import_ofx" ], "https://github.com/OCA/crm": [ "crm_stage_probability" ], "https://github.com/OCA/partner-contact": [ "partner_disable_gravatar", "partner_firstname" ], "https://github.com/OCA/project": [ "project_category", "project_status", "project_task_default_stage", "project_template", "project_timeline" ], "https://github.com/OCA/server-auth": [ "password_security" ], "https://github.com/OCA/server-brand": [ "disable_odoo_online", "remove_odoo_enterprise" ], "https://github.com/OCA/server-tools": [ "base_search_fuzzy", "module_change_auto_install" ], "https://github.com/OCA/server-ux": [ "base_technical_features", "date_range", "mass_editing" ], "https://github.com/OCA/social": [ "base_search_mail_content", "mail_debrand", "mail_tracking" ], "https://github.com/OCA/web": [ "web_environment_ribbon", "web_responsive", "web_no_bubble", "web_timeline" ] } class Style: """Set of console display styles. """ black = '\033[30m' red = '\033[31m' green = '\033[32m' yellow = '\033[33m' blue = '\033[34m' magenta = '\033[35m' cyan = '\033[36m' lightgray = '\033[37m' default = '\033[39m' bold = '\033[1m' normal = '\033[0m' def print_err(message): print(f"{Style.red}{Style.bold}err: {message}{Style.normal}" f"{Style.default}", file=sys.stderr) def check_pr(repo, module): conn = urllib.request.urlopen( f"{repo}/pulls?q=is%3Apr+is%3Aopen+%5B{version}%5D+mig+{module}") html = conn.read().decode('utf-8') result = re.search(rf"{repo[18:]}/pull/\d+", html) if result: return f"https://github.com{result.group()}" return None def load_repos(host_file): with open(host_file, 'r', encoding='utf8') as file: host = yaml.safe_load(file) for instance in host['odoo_nonprod_instances']: for repo in instance['custom_modules_oca']: repo_url = f"https://github.com/OCA/{repo['repo']}" if repos.get(repo_url): repos[repo_url].extend(repo['modules']) else: repos[repo_url] = repo['modules'] for repo in instance['custom_modules']: repos[f"https://sources.le-filament.com/lefilament/{repo}"] = None def print_help(): print(f"usage:\n {sys.argv[0]} <version> [hostvars file]\n" "options:\n <version> Odoo version\n" " [hostvars file] Ansible hostvars file to also check modules of" " this host") def main(): if len(sys.argv) >= 2: version = sys.argv[1] else: print_err("missing version argument (e.g.: '16.0')") sys.exit(1) if sys.argv[1] == '-h' or sys.argv[1] == '--help': print_help() sys.exit(0) if len(sys.argv) == 3: load_repos(sys.argv[2]) print(f"{Style.blue}💡 Checking availability Odoo v{version} " f"modules...{Style.default}\n") module_count = 0 available_module_count = 0 pr_count = 0 for repo, modules in repos.items(): tree = "tree" if 'github.com' not in repo: tree = '-/tree' repo_name = repo.split('/')[-1] for module in modules or []: module_count += 1 try: conn = urllib.request.urlopen(f"{repo}/{tree}/" f"{version}/{module}") except urllib.error.HTTPError as err: if err.code == 404: pr_url = check_pr(repo, module) if pr_url: pr_count += 1 print(f"{Style.red}❌ Not ready: {module} " f"(in {repo_name}){Style.default} " f"PR at {pr_url}") else: print(f"{Style.red}❌ Not ready: {module} " f"(in {repo_name}){Style.default}") else: print_err(f"HTTP error code {err.code} " f"for {module} (in {repo})") except urllib.error.URLError as err: print_err(f"HTTP error '{err.reason}' for {module} " f"(in {repo_name})") else: available_module_count += 1 print(f"{Style.green}✅ Available: {module} " f"(in {repo_name}){Style.default}") if not modules: module_count += 1 try: conn = urllib.request.urlopen(f"{repo}/{tree}/{version}/") except urllib.error.HTTPError as err: if err.code == 404: print(f"{Style.red}❌ Not already: {repo_name}" f"{Style.default}") else: print_err(f"HTTP error code {err.code} for {repo_name}") except urllib.error.URLError as err: print_err(f"HTTP error '{err.reason}' for {repo_name}") else: available_module_count += 1 print(f"{Style.green}✅ Available: {repo_name}" f"{Style.default}") if available_module_count < module_count: print(f"\n{Style.bold}Result: {Style.green}{available_module_count}" f"{Style.default} / {module_count} modules are available. " f"There are {pr_count} opened pull requests.{Style.normal}") else: print(f"\n{Style.green}{Style.bold}Result: all {module_count} modules " f"are available. It's ready to go!{Style.normal}{Style.default}") if __name__ == '__main__': main()