From 74be79eb418795a06847d157c253b397538e7f23 Mon Sep 17 00:00:00 2001 From: David-Lor <17401854+david-lor@users.noreply.github.com> Date: Fri, 2 Apr 2021 17:07:34 +0200 Subject: [PATCH 1/2] Style changes in setup_app script --- scripts/setup_app.py | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/scripts/setup_app.py b/scripts/setup_app.py index e7bec11..263f27b 100644 --- a/scripts/setup_app.py +++ b/scripts/setup_app.py @@ -14,28 +14,31 @@ class Settings: REQUIREMENTS_FILENAME = "requirements.txt" def __init__(self): + # Required settings try: self.app_name = os.environ["APP_NAME"] self.git_repository = os.environ["GIT_REPOSITORY"] except KeyError as error: raise Exception("Environment variable \"{}\" not defined!".format(error)) - + + # Dynamic settings self.base_dir = os.path.expanduser("~") self.first_run_file = self.join_home(self.FIRST_RUN_FILENAME) self.app_dir = self.join_home(self.app_name) self.requirements_file = self.join_app(self.REQUIREMENTS_FILENAME) + + # Optional settings self.git_branch = os.getenv("GIT_BRANCH") - + def join_home(self, path): return os.path.join(self.base_dir, path) - + def join_app(self, path): return os.path.join(self.app_dir, path) def log(message): - """Print log line with the current datetime - """ + """Print log line with the current datetime""" print("[{date}] {msg}".format( date=datetime.now().strftime("%y/%m/%d %H:%M:%S"), msg=message @@ -43,29 +46,25 @@ def log(message): def is_first_run(settings): - """Return True if this is the first time the container runs - """ + """Return True if this is the first time the container runs""" return not os.path.isfile(settings.first_run_file) def save_setup_done(settings): - """Store a file to mark this container already ran - """ + """Store a file to mark this container already ran""" os.mknod(settings.first_run_file) log("Saved 'App installed' status") def clear_output_dir(settings): - """Clear output directories - """ + """Clear output directories""" with suppress(FileNotFoundError): os.rmdir(settings.app_dir) log("Cleared output directories") def clone(settings): - """Clone the app through Git - """ + """Clone the app through Git""" log("Cloning app through Git...") branch_settings = [] if settings.git_branch: @@ -75,13 +74,12 @@ def clone(settings): if result > 0: # TODO capture git output when fail raise Exception("Git Clone failed!") - + log("App cloned through Git!") def install_requirements(settings): - """Install Python package requirements through git, from requirements file - """ + """Install Python package requirements through git, from requirements file""" if os.path.isfile(settings.requirements_file): log("Installing requirements through Pip...") result = subprocess.call(["pip", "install", "--user", "-r", settings.requirements_file]) @@ -94,8 +92,7 @@ def install_requirements(settings): def run(): - """Main run function - """ + """Main run function""" try: settings = Settings() args = (settings,) From bb6b1adbac3703e38da436ff6f87630925462404 Mon Sep 17 00:00:00 2001 From: David-Lor <17401854+David-Lor@users.noreply.github.com> Date: Wed, 7 Apr 2021 22:55:17 +0200 Subject: [PATCH 2/2] Clone SSH repositories using private key (first implementation) --- .dockerignore | 3 +++ .gitignore | 5 ++++- Dockerfile | 3 ++- Makefile | 4 ++++ README.md | 4 +++- scripts/setup_app.py | 41 ++++++++++++++++++++++++++++++++++++++++- 6 files changed, 56 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index 23b07bf..79487fe 100644 --- a/.dockerignore +++ b/.dockerignore @@ -6,3 +6,6 @@ tools/ examples/ .vscode/ .idea/ +ssh_key* +*.pem +*.pub diff --git a/.gitignore b/.gitignore index 0518d39..d9e741f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,7 @@ .vscode *pycache* .pytest_cache -*.pyc \ No newline at end of file +*.pyc +ssh_key* +*.pem +*.pub diff --git a/Dockerfile b/Dockerfile index b102a18..d4b31ed 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,8 @@ FROM python:${BASE_TAG} # ENV variables ENV GIT_REPOSITORY="" \ APP_NAME="App" \ - GIT_BRANCH="" + GIT_BRANCH="" \ + PRIVATE_KEY_LOCATION="" ARG USERNAME=user # Add a non-root user diff --git a/Makefile b/Makefile index c4688b3..fe246b7 100644 --- a/Makefile +++ b/Makefile @@ -36,5 +36,9 @@ push: ## push built image to dockerhub docker tag ${IMAGE_NAME} ${PUSH_IMAGE_NAME} docker push ${PUSH_IMAGE_NAME} +generate-ssh-key: ## create a new public and private key set in current directory + ssh-keygen -b 2048 -t rsa -f ./ssh_key -q -N "" + mv ./ssh_key ./ssh_key.pem + help: ## show this help. @fgrep -h "##" $(MAKEFILE_LIST) | fgrep -v fgrep | sed -e 's/\\$$//' | sed -e 's/##//' diff --git a/README.md b/README.md index c013664..3adf9fe 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,8 @@ The steps that run when the container starts are: ## Changelog +- 0.2.1 + - Allow cloning SSH repositories, and support using private keys - 0.1.1 - Upload to DockerHub from Github Workflow - Fix cmd in Dockerfile, change from `bash` to `sh` (Alpine compatibility) @@ -105,7 +107,7 @@ The steps that run when the container starts are: ## TODO - Allow setting GIT repository through CMD -- Load SSH private key for cloning SSH git repositories (from path or secret) +- Specify trusted host for SSH cloning - Create multi-arch images - Run as root with an env variable - or another image tag - Tag & upload images based on official Python image tags, plus versions of this repository diff --git a/scripts/setup_app.py b/scripts/setup_app.py index 263f27b..ee49c5c 100644 --- a/scripts/setup_app.py +++ b/scripts/setup_app.py @@ -4,6 +4,7 @@ """ import os +import shutil import subprocess from datetime import datetime from contextlib import suppress @@ -12,6 +13,7 @@ class Settings: FIRST_RUN_FILENAME = ".setup_app_done" REQUIREMENTS_FILENAME = "requirements.txt" + PRIVATE_KEY_WRITE_LOCATION = "/tmp/git_private_key.pem" def __init__(self): # Required settings @@ -29,6 +31,7 @@ def __init__(self): # Optional settings self.git_branch = os.getenv("GIT_BRANCH") + self.private_key_location = os.getenv("PRIVATE_KEY_LOCATION") def join_home(self, path): return os.path.join(self.base_dir, path) @@ -36,6 +39,10 @@ def join_home(self, path): def join_app(self, path): return os.path.join(self.app_dir, path) + @property + def git_repository_is_ssh(self): + return self.git_repository.startswith("git@") + def log(message): """Print log line with the current datetime""" @@ -63,14 +70,46 @@ def clear_output_dir(settings): log("Cleared output directories") +def get_private_key_file(settings): + """Return the location of the private key file, if any. + If specified as file, the file is copied to tmp and its permissions set to 600 so SSH does not complain about it.""" + private_key_read_filename = settings.private_key_location + if not private_key_read_filename: + return None + + try: + private_key_write_filename = settings.PRIVATE_KEY_WRITE_LOCATION + shutil.copy(private_key_read_filename, private_key_write_filename) + os.chmod(private_key_write_filename, 0o600) + + log("Private key read from \"\" and copied to \"\" with correct permissions".format(private_key_read_filename, private_key_write_filename)) + return private_key_write_filename + + except FileNotFoundError: + log("Private key in \"{}\" not found!".format(private_key_read_filename)) + + def clone(settings): """Clone the app through Git""" log("Cloning app through Git...") + cmd_env = dict() + branch_settings = [] if settings.git_branch: branch_settings = ["--branch", settings.git_branch] - result = subprocess.call(["git", "clone", *branch_settings, settings.git_repository, settings.app_dir]) + git_repository_is_ssh = settings.git_repository_is_ssh + if git_repository_is_ssh: + cmd_env["GIT_SSH_COMMAND"] = "ssh -o StrictHostKeyChecking=no" + + private_key_file = get_private_key_file(settings) + if private_key_file: + if not git_repository_is_ssh: + log("The specified repository is not a Git repository, but a private key was set. The key will not be used!") + else: + cmd_env["GIT_SSH_COMMAND"] += "-i {} -o IdentitiesOnly=yes".format(private_key_file) + + result = subprocess.call(["git", "clone", *branch_settings, settings.git_repository, settings.app_dir], env=cmd_env) if result > 0: # TODO capture git output when fail raise Exception("Git Clone failed!")