utilities/check_experimenter_and_start_jobs.py (139 lines of code) (raw):
import json
import os
import re
import subprocess
import time
from dateutil import parser
from datetime import datetime, timedelta
from packaging.version import parse, Version
from pathlib import Path
from collections import defaultdict
import requests
experimenter_url = "https://experimenter.services.mozilla.com/api/v6/experiments/?=status=Preview"
run_flag = False
testing_list = {}
path = Path().cwd()
versions = requests.get("https://whattrainisitnow.com/api/firefox/releases/").json()
def trigger_github_action(slug, branch, firefox_version, workflow_id):
url = f'https://api.github.com/repos/jrbenny35/klaatu/actions/workflows/{workflow_id}/dispatches'
inputs = {
'slug': slug,
'branch': branch,
'firefox-version': f"{firefox_version}"
}
headers = {
'Accept': 'application/vnd.github.v3+json',
'Authorization': f"Bearer {os.getenv('BEARER_TOKEN')}" ,
'X-GitHub-Api-Version': '2022-11-28'
}
data = {
'ref': 'main',
'inputs': inputs or {}
}
print(f"Running tests for {inputs['slug']}, with data {data}, on workflow {workflow_id}")
response = requests.post(url, headers=headers, data=json.dumps(data))
if response.status_code == 204:
print('Workflow triggered successfully!')
else:
print(f'Failed to trigger workflow: {response.status_code}')
print(response.text)
def get_latest_versions(versions, min_version):
# Parse versions and group by major.minor
version_list = []
from packaging.version import Version
for version in versions.keys():
if Version(version) >= Version(min_version[0]):
version_list.append(version)
version_groups = defaultdict(list)
for version_str in version_list:
version = parse(version_str)
major_minor = f"{version.major}.{version.minor}"
version_groups[major_minor].append(version)
# Determine the latest version in each group
latest_versions = {}
for major_minor, versions in version_groups.items():
latest_versions[major_minor] = max(versions, key=lambda v: (v.major, v.minor, v.micro))
# Return the latest versions sorted by major.minor
return [f"{version}" for version in sorted(latest_versions.values())]
def get_firefox_verions(app_name, channel, min_version):
test_versions = set()
non_desktop_beta = [f"{Version(list(versions.keys())[-1]).major +1}.0b"]
if "firefox_ios" in app_name:
# Get list of versions from requested to current based on whattrainisitnow
for version in reversed(versions.keys()):
version = Version(version)
if version.major > Version(min_version).major:
test_versions.add(version.major)
if not test_versions: # if the version doesn't exist in whattrainisitnow just return it
return [f"{Version(min_version)}"]
else:
return [f"{_}" for _ in test_versions if _ >= 128]
else:
match channel:
case "release":
test_versions = get_latest_versions(versions, min_version)
if "desktop" in app_name:
test_versions.extend(['latest', 'latest-beta'])
return test_versions
case "nightly":
return "['latest']"
case "beta":
if "desktop" in app_name:
return "['latest-beta']"
return non_desktop_beta
# Load string of last experiment
try:
with open('previous_experiment.txt') as f:
previous_experiment = f.read()
except FileNotFoundError:
subprocess.run([f"touch {path}"], encoding="utf8", shell=True)
previous_experiment = []
# Query Experimenter API
current_experiments = requests.get(experimenter_url).json()
# check if newest experiment is different
if current_experiments[-1]["slug"] not in previous_experiment:
run_flag = True
# Exit if the experiment is the same
if not run_flag:
exit
# Sorted list of experiments that have a publishedDate field
experiments = [_ for _ in current_experiments if _['publishedDate'] is not None]
experiments = sorted(experiments, key=lambda _: parser.isoparse(_.get('publishedDate')))
current_experiments = []
for experiment in experiments:
try:
if parser.parse(experiment.get("startDate")) >= datetime.now() - timedelta(days=7):
current_experiments.append(experiment)
except TypeError:
continue
temp_experiments_list = []
for count, item in enumerate(current_experiments):
if not item.get('isRollout'):
temp_experiments_list.append(item)
current_experiments = temp_experiments_list
# Trigger job based on application
# Get list of experiments to run tests on
for experiment in reversed(current_experiments):
if experiment["slug"] != previous_experiment:
testing_list[experiment["slug"]] = experiment
else:
break
for slug, data in testing_list.items():
ff_version = None
desktop_workflows = ["windows_manual.yml", "linux_manual.yml"]
try:
ff_version = [re.search(r"versionCompare\('(\d+).!'\)", data["targeting"]).group(1)]
except AttributeError:
continue # Don't test experiments with no target version
branches = f"{[item["slug"] for item in data["branches"]]}"
match data["appName"]:
case "firefox_desktop":
for workflow_id in desktop_workflows:
trigger_github_action(
slug, branches, get_firefox_verions(data["appName"], data["channel"], ff_version), workflow_id
)
case "firefox_ios":
workflow_id = "ios_manual.yml"
_ff_version = Version(ff_version[0])
trigger_github_action(
slug, branches, get_firefox_verions(data["appName"], data["channel"], f"{_ff_version.major}"), workflow_id
)
case "fenix":
workflow_id = "android_manual.yml"
_ff_version = Version(ff_version[0])
trigger_github_action(
slug, branches, get_firefox_verions(data["appName"], data["channel"], ff_version), workflow_id
)
time.sleep(30)
# Write last experiment to file for next cron run
with open('previous_experiment.txt', 'w') as f:
f.writelines(experiments[-1]["slug"])
print(f"Last experiment checked was {experiments[-1]['slug']}")