From e2ff7b337d894b4d96f971742667f27a00772ca6 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Wed, 28 Sep 2022 21:33:24 -0400 Subject: [PATCH 01/17] Convert init script from bash to python --- Dockerfile | 2 +- src/init.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/init.sh | 83 -------------------------------- 3 files changed, 135 insertions(+), 84 deletions(-) create mode 100644 src/init.py delete mode 100755 src/init.sh diff --git a/Dockerfile b/Dockerfile index ae9a855..dab29bb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -173,4 +173,4 @@ RUN ln -sf /proc/1/fd/1 /var/log/docker.log # Set the entry point to init.sh ################################ -ENTRYPOINT /root/init.sh +ENTRYPOINT ["python", "/root/init.py"] diff --git a/src/init.py b/src/init.py new file mode 100644 index 0000000..c5e4b7f --- /dev/null +++ b/src/init.py @@ -0,0 +1,134 @@ +import datetime +import glob +import os +import pathlib +import shutil +import subprocess +import tempfile + + +def getvar(var: str, check: bool = True) -> str: + val = os.getenv(var) + if check and val == "" or val is None: + raise ValueError('Environment variable "%s" has an invalid value.', var) + return val + + +def prepend_date(msg: str) -> str: + date = datetime.datetime.now().astimezone().strftime("%c %Z") + return "[%s] %s" % (date, msg) + + +def print_with_date(msg: str) -> None: + print(prepend_date(msg)) + + +def main() -> None: + # Copy the user scripts + root_scripts = "/root/user_scripts" + user_scripts = getvar("user_scripts_DIR") + os.makedirs(root_scripts) + shutil.copytree(user_scripts, root_scripts) + + # Delete non-root files + to_delete = [] + for filename in glob.iglob(os.path.join(root_scripts, "**/**"), recursive=True): + if os.path.isdir(filename): + continue + + # Check if not owned by root + f = pathlib.Path(filename) + if f.owner != "root": + print_with_date("File not owned by root. Removing %s", filename) + to_delete.append(filename) + continue + + # Check if non-root can write (group or other) + perm = oct(os.stat(filename).st_mode) + group_write = perm[-2] > "4" + other_write = perm[-1] > "4" + if group_write or other_write: + print_with_date("File writable by non root users. Removing %s", filename) + to_delete.append(filename) + + for f in to_delete: + os.remove(f) + + # Initialize CCache if it will be used + use_ccache = getvar("USE_CCACHE", False) == "1" + if use_ccache: + size = getvar("CCACHE_SIZE") + subprocess.run(["ccache", "-M", size], check=True, stderr=subprocess.STDOUT) + + # Initialize Git user information + subprocess.run( + ["git", "config", "--global", "user.name", getvar("USER_NAME")], check=True + ) + subprocess.run( + ["git", "config", "--global", "user.email", getvar("USER_MAIL")], check=True + ) + + sign_builds = bool(getvar("SIGN_BUILDS", False)) + if sign_builds: + key_dir = getvar("key_dir") + key_names = ["releasekey", "platform", "shared", "media", "networkstack"] + key_exts = [".pk8", ".x509.pem"] + + # Generate keys if directory empty + if len(os.listdir(key_dir)) == 0: + print_with_date( + "SIGN_BUILDS = true but empty $key_dir, generating new keys" + ) + keys_subj = getvar("KEYS_SUBJECT") + for k in key_names: + print_with_date("Generating %s..." % k) + subprocess.run( + ["/root/make_key", os.path.join(key_dir, k), keys_subj], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + ) + + # Check that all expected key files exist + for k in key_names: + for e in key_exts: + path = os.path.join(key_dir, k + e) + if not os.path.exists(path): + raise AssertionError( + prepend_date('Expected key file "%s" does not exist' % path) + ) + + for alias in ["cyngn-priv-app", "cyngn-app", "testkey"]: + for e in key_exts: + subprocess.run( + [ + "ln", + "-sf", + os.path.join(key_dir, "releasekey" + e), + os.path.join(key_dir, alias + e), + ], + check=True, + ) + + cron_time = getvar("CRONTAB_TIME") + if cron_time == "now": + subprocess.run(["/root/build.sh"], check=True) + else: + # Initialize the cronjob + cron_lines = [] + cron_lines.append("SHELL=/bin/bash\n") + for k, v in os.environ: + if k == "_" or v == "": + continue + cron_lines.append("%s=%s\n", k, v) + cron_lines.append( + "\n%s /usr/bin/flock -n /var/lock/build.lock /root/build.sh >> /var/log/docker.log 2>&1\n" + % cron_time, + ) + with tempfile.NamedTemporaryFile() as fp: + fp.writelines(cron_lines) + subprocess.run("crontab", fp.name, check=True) + + +if __name__ == "__main__": + main() diff --git a/src/init.sh b/src/init.sh deleted file mode 100755 index fb6e014..0000000 --- a/src/init.sh +++ /dev/null @@ -1,83 +0,0 @@ -#!/bin/bash - -# Docker init script -# Copyright (c) 2017 Julian Xhokaxhiu -# Copyright (C) 2017-2018 Nicola Corna -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -set -eEuo pipefail - -# Copy the user scripts -mkdir -p /root/userscripts -cp -r "$USERSCRIPTS_DIR"/. /root/userscripts -find /root/userscripts ! -type d ! -user root -exec echo ">> [$(date)] {} is not owned by root, removing" \; -exec rm {} \; -find /root/userscripts ! -type d -perm /g=w,o=w -exec echo ">> [$(date)] {} is writable by non-root users, removing" \; -exec rm {} \; - -# Initialize CCache if it will be used -if [ "$USE_CCACHE" = 1 ]; then - ccache -M "$CCACHE_SIZE" 2>&1 -fi - -# Initialize Git user information -git config --global user.name "$USER_NAME" -git config --global user.email "$USER_MAIL" - -if [ "$SIGN_BUILDS" = true ]; then - if [ -z "$(ls -A "$KEYS_DIR")" ]; then - echo ">> [$(date)] SIGN_BUILDS = true but empty \$KEYS_DIR, generating new keys" - for c in releasekey platform shared media networkstack sdk_sandbox bluetooth; do - echo ">> [$(date)] Generating $c..." - /root/make_key "$KEYS_DIR/$c" "$KEYS_SUBJECT" <<< '' &> /dev/null - done - else - for c in releasekey platform shared media networkstack; do - for e in pk8 x509.pem; do - if [ ! -f "$KEYS_DIR/$c.$e" ]; then - echo ">> [$(date)] SIGN_BUILDS = true and not empty \$KEYS_DIR, but \"\$KEYS_DIR/$c.$e\" is missing" - exit 1 - fi - done - done - - # those keys are only required starting with android-20, so people who have built earlier might not yet have them - for c in sdk_sandbox bluetooth; do - if [ ! -f "$KEYS_DIR/$c.pk8" ]; then - echo ">> [$(date)] Generating $c..." - /root/make_key "$KEYS_DIR/$c" "$KEYS_SUBJECT" <<< '' &> /dev/null - fi - done - fi - - for c in cyngn{-priv,}-app testkey; do - for e in pk8 x509.pem; do - ln -sf releasekey.$e "$KEYS_DIR/$c.$e" 2> /dev/null - done - done -fi - -if [ "$CRONTAB_TIME" = "now" ]; then - /root/build.sh -else - # Initialize the cronjob - cronFile=/tmp/buildcron - printf "SHELL=/bin/bash\n" > $cronFile - printenv -0 | sed -e 's/=\x0/=""\n/g' | sed -e 's/\x0/\n/g' | sed -e "s/_=/PRINTENV=/g" >> $cronFile - printf '\n%s /usr/bin/flock -n /var/lock/build.lock /root/build.sh >> /var/log/docker.log 2>&1\n' "$CRONTAB_TIME" >> $cronFile - crontab $cronFile - rm $cronFile - - # Run crond in foreground - cron -f 2>&1 -fi From e4be570be9110ffc550a374d46372ed09e848ba9 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Wed, 5 Oct 2022 11:54:50 -0400 Subject: [PATCH 02/17] Bug fixes - Fix environment variable names - Fix make_key call --- src/init.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/init.py b/src/init.py index c5e4b7f..4560a4e 100644 --- a/src/init.py +++ b/src/init.py @@ -10,7 +10,7 @@ import tempfile def getvar(var: str, check: bool = True) -> str: val = os.getenv(var) if check and val == "" or val is None: - raise ValueError('Environment variable "%s" has an invalid value.', var) + raise ValueError('Environment variable "%s" has an invalid value.' % var) return val @@ -26,8 +26,7 @@ def print_with_date(msg: str) -> None: def main() -> None: # Copy the user scripts root_scripts = "/root/user_scripts" - user_scripts = getvar("user_scripts_DIR") - os.makedirs(root_scripts) + user_scripts = getvar("USERSCRIPTS_DIR") shutil.copytree(user_scripts, root_scripts) # Delete non-root files @@ -70,14 +69,14 @@ def main() -> None: sign_builds = bool(getvar("SIGN_BUILDS", False)) if sign_builds: - key_dir = getvar("key_dir") + key_dir = getvar("KEYS_DIR") key_names = ["releasekey", "platform", "shared", "media", "networkstack"] key_exts = [".pk8", ".x509.pem"] # Generate keys if directory empty if len(os.listdir(key_dir)) == 0: print_with_date( - "SIGN_BUILDS = true but empty $key_dir, generating new keys" + "SIGN_BUILDS = true but empty $KEYS_DIR, generating new keys" ) keys_subj = getvar("KEYS_SUBJECT") for k in key_names: @@ -87,6 +86,7 @@ def main() -> None: stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=True, + input="\n".encode(), ) # Check that all expected key files exist From 0ca317bdb17fb5a03303c5085f29657cc2bc1945 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Sun, 9 Oct 2022 20:13:23 -0400 Subject: [PATCH 03/17] Fix SIGN_BUILDS parsing --- src/init.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/init.py b/src/init.py index 4560a4e..56638f9 100644 --- a/src/init.py +++ b/src/init.py @@ -7,9 +7,9 @@ import subprocess import tempfile -def getvar(var: str, check: bool = True) -> str: +def getvar(var: str) -> str: val = os.getenv(var) - if check and val == "" or val is None: + if val == "" or val is None: raise ValueError('Environment variable "%s" has an invalid value.' % var) return val @@ -54,7 +54,7 @@ def main() -> None: os.remove(f) # Initialize CCache if it will be used - use_ccache = getvar("USE_CCACHE", False) == "1" + use_ccache = getvar("USE_CCACHE") == "1" if use_ccache: size = getvar("CCACHE_SIZE") subprocess.run(["ccache", "-M", size], check=True, stderr=subprocess.STDOUT) @@ -67,7 +67,7 @@ def main() -> None: ["git", "config", "--global", "user.email", getvar("USER_MAIL")], check=True ) - sign_builds = bool(getvar("SIGN_BUILDS", False)) + sign_builds = getvar("SIGN_BUILDS").lower() == "true" if sign_builds: key_dir = getvar("KEYS_DIR") key_names = ["releasekey", "platform", "shared", "media", "networkstack"] From 9401fb99cb35585db8b97e52e9c475696dd1c53d Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Wed, 12 Oct 2022 18:33:05 -0400 Subject: [PATCH 04/17] Refactor init.py - replace crontab with apscheduler - replace print with logging - move build function into dedicated file (for mocking purposes in testing) --- Dockerfile | 4 +- src/build.py | 7 ++++ src/init.py | 108 ++++++++++++++++++++++++--------------------------- 3 files changed, 60 insertions(+), 59 deletions(-) create mode 100644 src/build.py diff --git a/Dockerfile b/Dockerfile index dab29bb..e309475 100644 --- a/Dockerfile +++ b/Dockerfile @@ -146,8 +146,8 @@ RUN apt-get -qq update && \ cron curl flex g++-multilib gcc-multilib git gnupg gperf imagemagick \ kmod lib32ncurses5-dev lib32readline-dev lib32z1-dev liblz4-tool \ libncurses5 libncurses5-dev libsdl1.2-dev libssl-dev libxml2 \ - libxml2-utils lsof lzop maven openjdk-8-jdk pngcrush procps python3 \ - python-is-python3 rsync schedtool squashfs-tools wget xdelta3 xsltproc yasm zip \ + libxml2-utils lsof lzop maven openjdk-8-jdk pngcrush procps python3 python3-apscheduler \ + python-is-python3 rsync schedtool squashfs-tools wget xdelta3 xsltproc yasm zip \ zlib1g-dev \ && rm -rf /var/lib/apt/lists/* diff --git a/src/build.py b/src/build.py new file mode 100644 index 0000000..3bf73a0 --- /dev/null +++ b/src/build.py @@ -0,0 +1,7 @@ +import subprocess + +def build() -> None: + subprocess.run(["/root/build.sh"], check=True, stderr=subprocess.STDOUT) + +if __name__ == "__main__": + build() \ No newline at end of file diff --git a/src/init.py b/src/init.py index 56638f9..edf2377 100644 --- a/src/init.py +++ b/src/init.py @@ -1,10 +1,14 @@ -import datetime import glob import os import pathlib import shutil import subprocess -import tempfile +from itertools import product +from build import build +from apscheduler.schedulers.blocking import BlockingScheduler +from apscheduler.triggers.cron import CronTrigger +import logging +import sys def getvar(var: str) -> str: @@ -14,16 +18,17 @@ def getvar(var: str) -> str: return val -def prepend_date(msg: str) -> str: - date = datetime.datetime.now().astimezone().strftime("%c %Z") - return "[%s] %s" % (date, msg) +def make_key(key_path: str, key_subj: str) -> None: + subprocess.run( + ["/root/make_key", key_path, key_subj], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=True, + input="\n".encode(), + ) -def print_with_date(msg: str) -> None: - print(prepend_date(msg)) - - -def main() -> None: +def init() -> None: # Copy the user scripts root_scripts = "/root/user_scripts" user_scripts = getvar("USERSCRIPTS_DIR") @@ -38,7 +43,7 @@ def main() -> None: # Check if not owned by root f = pathlib.Path(filename) if f.owner != "root": - print_with_date("File not owned by root. Removing %s", filename) + logging.warning("File not owned by root. Removing %s", filename) to_delete.append(filename) continue @@ -47,7 +52,7 @@ def main() -> None: group_write = perm[-2] > "4" other_write = perm[-1] > "4" if group_write or other_write: - print_with_date("File writable by non root users. Removing %s", filename) + logging.warning("File writable by non root users. Removing %s", filename) to_delete.append(filename) for f in to_delete: @@ -72,63 +77,52 @@ def main() -> None: key_dir = getvar("KEYS_DIR") key_names = ["releasekey", "platform", "shared", "media", "networkstack"] key_exts = [".pk8", ".x509.pem"] + key_aliases = ["cyngn-priv-app", "cyngn-app", "testkey"] # Generate keys if directory empty if len(os.listdir(key_dir)) == 0: - print_with_date( - "SIGN_BUILDS = true but empty $KEYS_DIR, generating new keys" - ) - keys_subj = getvar("KEYS_SUBJECT") + logging.info("SIGN_BUILDS = true but empty $KEYS_DIR, generating new keys") + key_subj = getvar("KEYS_SUBJECT") for k in key_names: - print_with_date("Generating %s..." % k) - subprocess.run( - ["/root/make_key", os.path.join(key_dir, k), keys_subj], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - check=True, - input="\n".encode(), - ) + logging.info("Generating %s..." % k) + make_key(os.path.join(key_dir, k), key_subj) # Check that all expected key files exist - for k in key_names: - for e in key_exts: - path = os.path.join(key_dir, k + e) - if not os.path.exists(path): - raise AssertionError( - prepend_date('Expected key file "%s" does not exist' % path) - ) + for k, e in product(key_names, key_exts): + path = os.path.join(key_dir, k + e) + if not os.path.exists(path): + raise AssertionError('Expected key file "%s" does not exist' % path) - for alias in ["cyngn-priv-app", "cyngn-app", "testkey"]: - for e in key_exts: - subprocess.run( - [ - "ln", - "-sf", - os.path.join(key_dir, "releasekey" + e), - os.path.join(key_dir, alias + e), - ], - check=True, - ) + # Create releasekey aliases + for a, e in product(key_aliases, key_exts): + src = os.path.join(key_dir, "releasekey" + e) + dst = os.path.join(key_dir, a + e) + os.symlink(src, dst) cron_time = getvar("CRONTAB_TIME") if cron_time == "now": - subprocess.run(["/root/build.sh"], check=True) + build() else: - # Initialize the cronjob - cron_lines = [] - cron_lines.append("SHELL=/bin/bash\n") - for k, v in os.environ: - if k == "_" or v == "": - continue - cron_lines.append("%s=%s\n", k, v) - cron_lines.append( - "\n%s /usr/bin/flock -n /var/lock/build.lock /root/build.sh >> /var/log/docker.log 2>&1\n" - % cron_time, + scheduler = BlockingScheduler() + scheduler.add_job( + func=build, + trigger=CronTrigger.from_crontab(cron_time), + misfire_grace_time=None, # Allow job to run as long as it needs + coalesce=True, + max_instances=1, # Allow only one concurrent instance ) - with tempfile.NamedTemporaryFile() as fp: - fp.writelines(cron_lines) - subprocess.run("crontab", fp.name, check=True) + + # Run forever + scheduler.start() if __name__ == "__main__": - main() + + logging.basicConfig( + stream=sys.stdout, + level=logging.INFO, + format="[%(asctime)s] %(levelname)s %(message)s", + datefmt="%c %Z", + ) + + init() From 7d21e419434e0b6b3136285584626e93fbb1c0fd Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Fri, 14 Oct 2022 11:41:48 -0400 Subject: [PATCH 05/17] Add test for key generation - confirm keyfiles are make when SIGN_BUILD is true - run with `docker run --entrypoint pytest-3 [image] /root` --- Dockerfile | 2 +- src/init_test.py | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/init_test.py diff --git a/Dockerfile b/Dockerfile index e309475..e8e0e13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -147,7 +147,7 @@ RUN apt-get -qq update && \ kmod lib32ncurses5-dev lib32readline-dev lib32z1-dev liblz4-tool \ libncurses5 libncurses5-dev libsdl1.2-dev libssl-dev libxml2 \ libxml2-utils lsof lzop maven openjdk-8-jdk pngcrush procps python3 python3-apscheduler \ - python-is-python3 rsync schedtool squashfs-tools wget xdelta3 xsltproc yasm zip \ + python3-pytest python-is-python3 rsync schedtool squashfs-tools wget xdelta3 xsltproc yasm zip \ zlib1g-dev \ && rm -rf /var/lib/apt/lists/* diff --git a/src/init_test.py b/src/init_test.py new file mode 100644 index 0000000..9a69e18 --- /dev/null +++ b/src/init_test.py @@ -0,0 +1,21 @@ +import build +import init +import os +from itertools import product + + +def test_key_gen(monkeypatch): + def mock_build() -> None: + print("mock build") + + monkeypatch.setenv("SIGN_BUILDS", "true") + monkeypatch.setattr(build, "build", mock_build) + + init.init() + + # Confirm all keys are generated + key_names = ["releasekey", "platform", "shared", "media", "networkstack"] + key_exts = [".pk8", ".x509.pem"] + for k, e in product(key_names, key_exts): + path = os.path.join("/srv/keys", k + e) + assert os.path.exists(path) From 7bdc6675e139fed61c859d633ea9354dd64663bd Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 8 Nov 2022 15:42:56 -0500 Subject: [PATCH 06/17] Migrate to pathlib from os.path --- src/init.py | 42 ++++++++++++++++++++---------------------- src/init_test.py | 6 +++--- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/src/init.py b/src/init.py index edf2377..b0f5c76 100644 --- a/src/init.py +++ b/src/init.py @@ -1,8 +1,7 @@ -import glob -import os -import pathlib +from os import getenv import shutil import subprocess +from pathlib import Path from itertools import product from build import build from apscheduler.schedulers.blocking import BlockingScheduler @@ -12,7 +11,7 @@ import sys def getvar(var: str) -> str: - val = os.getenv(var) + val = getenv(var) if val == "" or val is None: raise ValueError('Environment variable "%s" has an invalid value.' % var) return val @@ -36,27 +35,26 @@ def init() -> None: # Delete non-root files to_delete = [] - for filename in glob.iglob(os.path.join(root_scripts, "**/**"), recursive=True): - if os.path.isdir(filename): + for path in Path(root_scripts).rglob("*"): + if path.isdir(path): continue # Check if not owned by root - f = pathlib.Path(filename) - if f.owner != "root": - logging.warning("File not owned by root. Removing %s", filename) - to_delete.append(filename) + if path.owner != "root": + logging.warning("File not owned by root. Removing %s", path) + to_delete.append(path) continue # Check if non-root can write (group or other) - perm = oct(os.stat(filename).st_mode) + perm = oct(path.stat().st_mode) group_write = perm[-2] > "4" other_write = perm[-1] > "4" if group_write or other_write: - logging.warning("File writable by non root users. Removing %s", filename) - to_delete.append(filename) + logging.warning("File writable by non root users. Removing %s", path) + to_delete.append(path) for f in to_delete: - os.remove(f) + f.unlink() # Initialize CCache if it will be used use_ccache = getvar("USE_CCACHE") == "1" @@ -74,30 +72,30 @@ def init() -> None: sign_builds = getvar("SIGN_BUILDS").lower() == "true" if sign_builds: - key_dir = getvar("KEYS_DIR") + key_dir = Path(getvar("KEYS_DIR")) key_names = ["releasekey", "platform", "shared", "media", "networkstack"] key_exts = [".pk8", ".x509.pem"] key_aliases = ["cyngn-priv-app", "cyngn-app", "testkey"] # Generate keys if directory empty - if len(os.listdir(key_dir)) == 0: + if not list(key_dir.glob("*")): logging.info("SIGN_BUILDS = true but empty $KEYS_DIR, generating new keys") key_subj = getvar("KEYS_SUBJECT") for k in key_names: logging.info("Generating %s..." % k) - make_key(os.path.join(key_dir, k), key_subj) + make_key(str(key_dir.joinpath(k)), key_subj) # Check that all expected key files exist for k, e in product(key_names, key_exts): - path = os.path.join(key_dir, k + e) - if not os.path.exists(path): + path = key_dir.joinpath(k).with_suffix(e) + if not path.exists(): raise AssertionError('Expected key file "%s" does not exist' % path) # Create releasekey aliases for a, e in product(key_aliases, key_exts): - src = os.path.join(key_dir, "releasekey" + e) - dst = os.path.join(key_dir, a + e) - os.symlink(src, dst) + src = key_dir.joinpath("releasekey").with_suffix(e) + dst = key_dir.joinpath(a).with_suffix(e) + dst.symlink_to(src) cron_time = getvar("CRONTAB_TIME") if cron_time == "now": diff --git a/src/init_test.py b/src/init_test.py index 9a69e18..7284e96 100644 --- a/src/init_test.py +++ b/src/init_test.py @@ -1,7 +1,7 @@ import build import init -import os from itertools import product +from pathlib import Path def test_key_gen(monkeypatch): @@ -17,5 +17,5 @@ def test_key_gen(monkeypatch): key_names = ["releasekey", "platform", "shared", "media", "networkstack"] key_exts = [".pk8", ".x509.pem"] for k, e in product(key_names, key_exts): - path = os.path.join("/srv/keys", k + e) - assert os.path.exists(path) + path = Path("/srv/keys").joinpath(k).with_suffix(e) + assert path.exists(path) From 4a248c5b9ab28448ec547fe77715200a7b7e9e0c Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 8 Nov 2022 15:45:19 -0500 Subject: [PATCH 07/17] bug fix - change how build is called so pytest mock works --- src/init.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/init.py b/src/init.py index b0f5c76..438f8d4 100644 --- a/src/init.py +++ b/src/init.py @@ -3,7 +3,7 @@ import shutil import subprocess from pathlib import Path from itertools import product -from build import build +import build from apscheduler.schedulers.blocking import BlockingScheduler from apscheduler.triggers.cron import CronTrigger import logging @@ -99,11 +99,11 @@ def init() -> None: cron_time = getvar("CRONTAB_TIME") if cron_time == "now": - build() + build.build() else: scheduler = BlockingScheduler() scheduler.add_job( - func=build, + func=build.build, trigger=CronTrigger.from_crontab(cron_time), misfire_grace_time=None, # Allow job to run as long as it needs coalesce=True, From 0b4649858d0d57cafe3d5c1d5d8fc7e7653510f1 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 8 Nov 2022 16:12:09 -0500 Subject: [PATCH 08/17] Bug fix - fix path.exists call --- src/init_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init_test.py b/src/init_test.py index 7284e96..66820c4 100644 --- a/src/init_test.py +++ b/src/init_test.py @@ -18,4 +18,4 @@ def test_key_gen(monkeypatch): key_exts = [".pk8", ".x509.pem"] for k, e in product(key_names, key_exts): path = Path("/srv/keys").joinpath(k).with_suffix(e) - assert path.exists(path) + assert path.exists() From b8888caef5734e73d44b61aaed0670d30136e965 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 8 Nov 2022 16:14:57 -0500 Subject: [PATCH 09/17] Create pytest.yml --- .github/workflows/pytest.yml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 .github/workflows/pytest.yml diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml new file mode 100644 index 0000000..960bf3f --- /dev/null +++ b/.github/workflows/pytest.yml @@ -0,0 +1,19 @@ +name: pytest + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Build docker container + run: docker build -t microgdev . + + - name: Run tests + run: docker run --entrypoint pytest-3 microgdev /root From dd17f86c2688a310161ce20869895b1a8b3e69f8 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 8 Nov 2022 16:21:28 -0500 Subject: [PATCH 10/17] Update pytest.yml --- .github/workflows/pytest.yml | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 960bf3f..f0c9392 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -7,13 +7,12 @@ on: jobs: build: runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 - steps: - - name: Checkout - uses: actions/checkout@v3 + - name: Build docker container + run: docker build -t microgdev . - - name: Build docker container - run: docker build -t microgdev . - - - name: Run tests - run: docker run --entrypoint pytest-3 microgdev /root + - name: Run tests + run: docker run --entrypoint pytest-3 microgdev /root From 614c612d39dae82acfc14ff00b7bc60aae6c2348 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 8 Nov 2022 16:29:22 -0500 Subject: [PATCH 11/17] Update pytest.yml --- .github/workflows/pytest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f0c9392..97e1ca5 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -12,7 +12,7 @@ jobs: uses: actions/checkout@v3 - name: Build docker container - run: docker build -t microgdev . + run: docker build -t docker-lineage-cicd-temp . - name: Run tests - run: docker run --entrypoint pytest-3 microgdev /root + run: docker run --entrypoint pytest-3 docker-lineage-cicd-temp /root From ff613d39d5f8181bf4ea1b05ff3189a5ebf6af61 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Thu, 10 Nov 2022 08:49:32 -0500 Subject: [PATCH 12/17] Rename job in ci --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 97e1ca5..f25da50 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -5,7 +5,7 @@ on: pull_request: jobs: - build: + pytest: runs-on: ubuntu-latest steps: - name: Checkout From 0bad8dcdb7c2e3b1bbc75a255b52c3450c244e49 Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Fri, 25 Nov 2022 09:03:01 -0500 Subject: [PATCH 13/17] Accept 'true' in USE_CCACHE variable --- src/init.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init.py b/src/init.py index 438f8d4..f6bfc90 100644 --- a/src/init.py +++ b/src/init.py @@ -57,7 +57,7 @@ def init() -> None: f.unlink() # Initialize CCache if it will be used - use_ccache = getvar("USE_CCACHE") == "1" + use_ccache = getvar("USE_CCACHE").lower() in ['1', 'true'] if use_ccache: size = getvar("CCACHE_SIZE") subprocess.run(["ccache", "-M", size], check=True, stderr=subprocess.STDOUT) From a446bc58b4b655be18949436214edbc2bccbfcba Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Fri, 25 Nov 2022 09:34:37 -0500 Subject: [PATCH 14/17] Move pytest to /test folder --- Dockerfile | 1 + {src => test}/init_test.py | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) rename {src => test}/init_test.py (93%) diff --git a/Dockerfile b/Dockerfile index e8e0e13..d34a377 100644 --- a/Dockerfile +++ b/Dockerfile @@ -162,6 +162,7 @@ RUN echo "jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1 # Copy required files ##################### COPY src/ /root/ +COPY test/ /test/ # Set the work directory ######################## diff --git a/src/init_test.py b/test/init_test.py similarity index 93% rename from src/init_test.py rename to test/init_test.py index 66820c4..569d988 100644 --- a/src/init_test.py +++ b/test/init_test.py @@ -1,8 +1,11 @@ -import build -import init +import sys from itertools import product from pathlib import Path +sys.path.append("/root") +import build +import init + def test_key_gen(monkeypatch): def mock_build() -> None: From 26d84402ec48ab456033e09a1ed70b91c76bec0d Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Fri, 25 Nov 2022 09:38:32 -0500 Subject: [PATCH 15/17] Update pytest workflow with new /test directory --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f25da50..64f27c6 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -15,4 +15,4 @@ jobs: run: docker build -t docker-lineage-cicd-temp . - name: Run tests - run: docker run --entrypoint pytest-3 docker-lineage-cicd-temp /root + run: docker run --entrypoint pytest-3 docker-lineage-cicd-temp /test From b97691d2251bcfe15c942ddbd003f6ab5169bb1a Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 29 Nov 2022 21:23:05 -0500 Subject: [PATCH 16/17] Refactor init.py - create Init class - Update behavior to match lineage-20 init.sh (new keys) --- src/init.py | 213 ++++++++++++++++++++++++++-------------------- test/init_test.py | 7 +- 2 files changed, 125 insertions(+), 95 deletions(-) diff --git a/src/init.py b/src/init.py index f6bfc90..dbd351c 100644 --- a/src/init.py +++ b/src/init.py @@ -27,100 +27,131 @@ def make_key(key_path: str, key_subj: str) -> None: ) -def init() -> None: - # Copy the user scripts - root_scripts = "/root/user_scripts" - user_scripts = getvar("USERSCRIPTS_DIR") - shutil.copytree(user_scripts, root_scripts) +class Init: + def __init__(self): + self.root_scripts = "/root/user_scripts" + self.user_scripts = getvar("USERSCRIPTS_DIR") + self.use_ccache = getvar("USE_CCACHE").lower() in ["1", "true"] + self.sign_builds = getvar("SIGN_BUILDS").lower() == "true" + if self.sign_builds: + self.key_dir = Path(getvar("KEYS_DIR")) + if self.use_ccache: + self.ccache_size = getvar("CCACHE_SIZE") + self.cron_time = getvar("CRONTAB_TIME") + self.git_username = getvar("USER_NAME") + self.git_email = getvar("USER_MAIL") + self.key_subj = getvar("KEYS_SUBJECT") + self.key_names = [ + "releasekey", + "platform", + "shared", + "media", + "networkstack", + "sdk_sandbox", + "bluetooth", + ] + self.key_exts = [".pk8", ".x509.pem"] + self.key_aliases = ["cyngn-priv-app", "cyngn-app", "testkey"] + # New keys needed as of LOS20 + self.new_key_names = [ + "sdk_sandbox", + "bluetooth", + ] - # Delete non-root files - to_delete = [] - for path in Path(root_scripts).rglob("*"): - if path.isdir(path): - continue - - # Check if not owned by root - if path.owner != "root": - logging.warning("File not owned by root. Removing %s", path) - to_delete.append(path) - continue - - # Check if non-root can write (group or other) - perm = oct(path.stat().st_mode) - group_write = perm[-2] > "4" - other_write = perm[-1] > "4" - if group_write or other_write: - logging.warning("File writable by non root users. Removing %s", path) - to_delete.append(path) - - for f in to_delete: - f.unlink() - - # Initialize CCache if it will be used - use_ccache = getvar("USE_CCACHE").lower() in ['1', 'true'] - if use_ccache: - size = getvar("CCACHE_SIZE") - subprocess.run(["ccache", "-M", size], check=True, stderr=subprocess.STDOUT) - - # Initialize Git user information - subprocess.run( - ["git", "config", "--global", "user.name", getvar("USER_NAME")], check=True - ) - subprocess.run( - ["git", "config", "--global", "user.email", getvar("USER_MAIL")], check=True - ) - - sign_builds = getvar("SIGN_BUILDS").lower() == "true" - if sign_builds: - key_dir = Path(getvar("KEYS_DIR")) - key_names = ["releasekey", "platform", "shared", "media", "networkstack"] - key_exts = [".pk8", ".x509.pem"] - key_aliases = ["cyngn-priv-app", "cyngn-app", "testkey"] - - # Generate keys if directory empty - if not list(key_dir.glob("*")): - logging.info("SIGN_BUILDS = true but empty $KEYS_DIR, generating new keys") - key_subj = getvar("KEYS_SUBJECT") - for k in key_names: - logging.info("Generating %s..." % k) - make_key(str(key_dir.joinpath(k)), key_subj) - - # Check that all expected key files exist - for k, e in product(key_names, key_exts): - path = key_dir.joinpath(k).with_suffix(e) - if not path.exists(): - raise AssertionError('Expected key file "%s" does not exist' % path) - - # Create releasekey aliases - for a, e in product(key_aliases, key_exts): - src = key_dir.joinpath("releasekey").with_suffix(e) - dst = key_dir.joinpath(a).with_suffix(e) - dst.symlink_to(src) - - cron_time = getvar("CRONTAB_TIME") - if cron_time == "now": - build.build() - else: - scheduler = BlockingScheduler() - scheduler.add_job( - func=build.build, - trigger=CronTrigger.from_crontab(cron_time), - misfire_grace_time=None, # Allow job to run as long as it needs - coalesce=True, - max_instances=1, # Allow only one concurrent instance + logging.basicConfig( + stream=sys.stdout, + level=logging.INFO, + format="[%(asctime)s] %(levelname)s %(message)s", + datefmt="%c %Z", ) - # Run forever - scheduler.start() + def generate_key(self, key_name: str): + logging.info("Generating %s..." % key_name) + make_key(str(self.key_dir.joinpath(key_name)), self.key_subj) + + def do(self): + # Copy the user scripts + shutil.copytree(self.user_scripts, self.root_scripts) + + # Delete non-root files + to_delete = [] + for path in Path(self.root_scripts).rglob("*"): + if path.isdir(path): + continue + + # Check if not owned by root + if path.owner != "root": + logging.warning("File not owned by root. Removing %s", path) + to_delete.append(path) + continue + + # Check if non-root can write (group or other) + perm = oct(path.stat().st_mode) + group_write = perm[-2] > "4" + other_write = perm[-1] > "4" + if group_write or other_write: + logging.warning("File writable by non root users. Removing %s", path) + to_delete.append(path) + + for f in to_delete: + f.unlink() + + # Initialize CCache if it will be used + if self.use_ccache: + subprocess.run( + ["ccache", "-M", self.ccache_size], check=True, stderr=subprocess.STDOUT + ) + + # Initialize Git user information + subprocess.run( + ["git", "config", "--global", "user.name", self.git_username], check=True + ) + subprocess.run( + ["git", "config", "--global", "user.email", self.git_email], check=True + ) + + if self.sign_builds: + # Generate keys if directory empty + if not list(self.key_dir.glob("*")): + logging.info( + "SIGN_BUILDS = true but empty $KEYS_DIR, generating new keys" + ) + for k in self.key_names: + self.generate_key(k) + + # Check that all expected key files exist. If a LOS20 key does not exist, create it. + for k, e in product(self.key_names, self.key_exts): + path = self.key_dir.joinpath(k).with_suffix(e) + if not path.exists(): + if k in self.new_key_names: + self.generate_key(k) + else: + raise AssertionError( + 'Expected key file "%s" does not exist' % path + ) + + # Create releasekey aliases + for a, e in product(self.key_aliases, self.key_exts): + src = self.key_dir.joinpath("releasekey").with_suffix(e) + dst = self.key_dir.joinpath(a).with_suffix(e) + dst.symlink_to(src) + + if self.cron_time == "now": + build.build() + else: + scheduler = BlockingScheduler() + scheduler.add_job( + func=build.build, + trigger=CronTrigger.from_crontab(self.cron_time), + misfire_grace_time=None, # Allow job to run as long as it needs + coalesce=True, + max_instances=1, # Allow only one concurrent instance + ) + + # Run forever + scheduler.start() if __name__ == "__main__": - - logging.basicConfig( - stream=sys.stdout, - level=logging.INFO, - format="[%(asctime)s] %(levelname)s %(message)s", - datefmt="%c %Z", - ) - - init() + initialize = Init() + initialize.do() diff --git a/test/init_test.py b/test/init_test.py index 569d988..bada3f5 100644 --- a/test/init_test.py +++ b/test/init_test.py @@ -14,11 +14,10 @@ def test_key_gen(monkeypatch): monkeypatch.setenv("SIGN_BUILDS", "true") monkeypatch.setattr(build, "build", mock_build) - init.init() + initialize = init.Init() + initialize.do() # Confirm all keys are generated - key_names = ["releasekey", "platform", "shared", "media", "networkstack"] - key_exts = [".pk8", ".x509.pem"] - for k, e in product(key_names, key_exts): + for k, e in product(initialize.key_names, initialize.key_exts): path = Path("/srv/keys").joinpath(k).with_suffix(e) assert path.exists() From 6646fd47390cf91948f9889eff000351479699aa Mon Sep 17 00:00:00 2001 From: Travis Burrows <37048572+tjburrows@users.noreply.github.com> Date: Tue, 29 Nov 2022 21:43:03 -0500 Subject: [PATCH 17/17] Bug fixes - add missing type hints - fix path.is_dir() call --- src/init.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/init.py b/src/init.py index dbd351c..6028902 100644 --- a/src/init.py +++ b/src/init.py @@ -28,7 +28,7 @@ def make_key(key_path: str, key_subj: str) -> None: class Init: - def __init__(self): + def __init__(self) -> None: self.root_scripts = "/root/user_scripts" self.user_scripts = getvar("USERSCRIPTS_DIR") self.use_ccache = getvar("USE_CCACHE").lower() in ["1", "true"] @@ -65,18 +65,18 @@ class Init: datefmt="%c %Z", ) - def generate_key(self, key_name: str): + def generate_key(self, key_name: str) -> None: logging.info("Generating %s..." % key_name) make_key(str(self.key_dir.joinpath(key_name)), self.key_subj) - def do(self): + def do(self) -> None: # Copy the user scripts shutil.copytree(self.user_scripts, self.root_scripts) # Delete non-root files to_delete = [] for path in Path(self.root_scripts).rglob("*"): - if path.isdir(path): + if path.is_dir(): continue # Check if not owned by root