#!/usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, # You can obtain one at http://mozilla.org/MPL/2.0/. import sys sys.path.append("/builds/worker/checkouts/gecko/third_party/python") sys.path.append(".") import base64 import os import platform import signal import stat import subprocess import requests import taskcluster # Bump this number when you need to cause a commit for the job to re-run: 21 if len(sys.argv) < 3: print("Usage:", sys.argv[0], "gecko-dev-path updatebot-path [moz-fetches-dir]") sys.exit(1) GECKO_DEV_PATH = sys.argv[1].replace("/", os.path.sep) UPDATEBOT_PATH = sys.argv[2].replace("/", os.path.sep) # Only needed on Windows if len(sys.argv) > 3: FETCHES_PATH = sys.argv[3].replace("/", os.path.sep) else: FETCHES_PATH = None HOME_PATH = os.path.expanduser("~") OPERATING_MODE = ( "prod" if os.environ.get("GECKO_HEAD_REPOSITORY", "") == "https://hg.mozilla.org/mozilla-central" else "dev" ) DEV_PHAB_URL = "https://phabricator-dev.allizom.org/" PROD_PHAB_URL = "https://phabricator.services.mozilla.com/" phabricator_url = DEV_PHAB_URL if OPERATING_MODE == "dev" else PROD_PHAB_URL def log(*args): print(*args) def get_secret(name): secret = None if "TASK_ID" in os.environ: secrets_url = ( "http://taskcluster/secrets/v1/secret/project/updatebot/" + ("3" if OPERATING_MODE == "prod" else "2") + "/" + name ) res = requests.get(secrets_url) res.raise_for_status() secret = res.json() else: secrets = taskcluster.Secrets(taskcluster.optionsFromEnvironment()) secret = secrets.get("project/updatebot/" + OPERATING_MODE + "/" + name) secret = secret["secret"] if "secret" in secret else None secret = secret["value"] if "value" in secret else None return secret # Get TC Secrets ======================================= log("Operating mode is ", OPERATING_MODE) log("Getting secrets...") bugzilla_api_key = get_secret("bugzilla-api-key") phabricator_token = get_secret("phabricator-token") try_sshkey = get_secret("try-sshkey") database_config = get_secret("database-password") sentry_url = get_secret("sentry-url") sql_proxy_config = get_secret("sql-proxy-config") # Update Updatebot ======================================= if OPERATING_MODE == "dev": """ If we are in development mode, we will update from github. (This command will probably only work if we checkout a branch FWIW.) This allows us to iterate faster by committing to github and re-running the cron job on Taskcluster, without rebuilding the Docker image. However, this mechanism is bypassing the security feature we have in-tree, where upstream out-of-tree code is fixed at a known revision and cannot be changed without a commit to m-c. Therefore, we only do this in dev mode when running on try. """ os.chdir(UPDATEBOT_PATH) log("Performing git repo update...") command = ["git", "symbolic-ref", "-q", "HEAD"] r = subprocess.run(command) if r.returncode == 0: # This indicates we are on a branch, and not a specific revision subprocess.check_call(["git", "pull", "origin"]) # Set Up SSH & Phabricator ============================== os.chdir(HOME_PATH) log("Setting up ssh and phab keys...") with open("id_rsa", "w") as sshkey: sshkey.write(try_sshkey) os.chmod("id_rsa", stat.S_IRUSR | stat.S_IWUSR) arc_filename = ".arcrc" if platform.system() == "Windows": arc_path = os.path.join(FETCHES_PATH, "..", "AppData", "Roaming") os.makedirs(arc_path, exist_ok=True) os.chdir(arc_path) log("Writing %s to %s" % (arc_filename, arc_path)) else: os.chdir(HOME_PATH) arcrc = open(arc_filename, "w") towrite = """ { "hosts": { "PHAB_URL_HERE": { "token": "TOKENHERE" } } } """.replace( "TOKENHERE", phabricator_token ).replace( "PHAB_URL_HERE", phabricator_url + "api/" ) arcrc.write(towrite) arcrc.close() os.chmod(arc_filename, stat.S_IRUSR | stat.S_IWUSR) # Set up the Cloud SQL Proxy ============================= os.chdir(HOME_PATH) log("Setting up cloud_sql_proxy...") with open("sql-proxy-key", "w") as proxy_key_file: proxy_key_file.write( base64.b64decode(sql_proxy_config["key-value"]).decode("utf-8") ) instance_name = sql_proxy_config["instance-name"] if platform.system() == "Linux": sql_proxy_command = "cloud_sql_proxy" else: sql_proxy_command = os.path.join(UPDATEBOT_PATH, "..", "cloud_sql_proxy.exe") sql_proxy_command += ( " -instances=" + instance_name + "=tcp:3306 -credential_file=sql-proxy-key" ) sql_proxy_args = { "stdout": subprocess.PIPE, "stderr": subprocess.PIPE, "shell": True, "start_new_session": True, } if platform.system() == "Windows": si = subprocess.STARTUPINFO() si.dwFlags = subprocess.CREATE_NEW_PROCESS_GROUP sql_proxy_args["startupinfo"] = si sql_proxy = subprocess.Popen((sql_proxy_command), **sql_proxy_args) try: (stdout, stderr) = sql_proxy.communicate(input=None, timeout=2) log("sql proxy stdout:", stdout.decode("utf-8")) log("sql proxy stderr:", stderr.decode("utf-8")) except subprocess.TimeoutExpired: log("no sqlproxy output in 2 seconds, this means it probably didn't error.") log("sqlproxy pid:", sql_proxy.pid) database_config["host"] = "127.0.0.1" # Vendor ================================================= log("Getting Updatebot ready...") os.chdir(UPDATEBOT_PATH) localconfig = { "General": { "env": OPERATING_MODE, "gecko-path": GECKO_DEV_PATH, }, "Logging": { "local": True, "sentry": True, "sentry_config": {"url": sentry_url, "debug": False}, }, "Database": database_config, "Bugzilla": { "apikey": bugzilla_api_key, }, "Taskcluster": { "url_treeherder": "https://treeherder.mozilla.org/", "url_taskcluster": "http://taskcluster/", }, } log("Writing local config file") config = open("localconfig.py", "w") config.write("localconfig = " + str(localconfig)) config.close() log("Running updatebot") # On Windows, Updatebot is run by windows-setup.sh if platform.system() == "Linux": subprocess.check_call(["python3", "-m", "poetry", "run", "./automation.py"]) # Clean up =============================================== log("Killing cloud_sql_proxy") os.kill(sql_proxy.pid, signal.SIGTERM)