From 22302a0c9052d8568c98ae417831ef9509e7ddc1 Mon Sep 17 00:00:00 2001 From: Isaac Boehman Date: Fri, 9 Aug 2019 23:56:41 -0700 Subject: [PATCH] dockerfile + wrapper script --- .dockerignore | 2 + Dockerfile | 13 ++++ apis/doesthedogdie.py | 6 +- build_json.py | 10 ++- run-in-docker.py | 146 ++++++++++++++++++++++++++++++++++++++++++ write_to_plex.py | 16 +++-- 6 files changed, 184 insertions(+), 9 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 run-in-docker.py diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..8a98b3b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +.git +screenshots diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..18ca008 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.6 + +WORKDIR /code + +COPY requirements.txt /code + +RUN pip install -r requirements.txt +COPY run.sh /code/ + +COPY build_json.py dtdd_api.py write_to_plex.py /code/ +COPY apis/* /code/apis/ +COPY config.py.example config.py* /code/ +ENTRYPOINT ["python"] diff --git a/apis/doesthedogdie.py b/apis/doesthedogdie.py index be76113..bb86e66 100644 --- a/apis/doesthedogdie.py +++ b/apis/doesthedogdie.py @@ -4,10 +4,10 @@ import time import json try: - from config import use_memcache + from .config import use_memcache if use_memcache: try: - from config import memcache_address, memcache_port, invalidation_time + from .config import memcache_address, memcache_port, invalidation_time except ImportError: print("⚠ Please set memcache_address, memcache_port and invalidation_time in config.py") use_memcache = False @@ -90,4 +90,4 @@ def get_info_for_movie(movie_name, use_cache=True): client.set(movie_name, json.dumps(dict(data=data, time_retrieved=int(time.time())))) else: data = None - return data \ No newline at end of file + return data diff --git a/build_json.py b/build_json.py index e023690..4df8746 100644 --- a/build_json.py +++ b/build_json.py @@ -1,10 +1,16 @@ from apis.doesthedogdie import get_info_for_movie from apis.plex import get_movies_and_format +import argparse import json import requests import urllib.parse from tqdm import tqdm + +parser = argparse.ArgumentParser() +parser.add_argument("--output", default="movies.json", + help="relative or absolute path to write DTDD json info to") + try: from config import only_show_yes except: @@ -32,7 +38,6 @@ print("⚠ Please set use_short_names in your config.py") use_short_names = False - def yes_or_no_formatter(topic): action = "Unsure" @@ -43,6 +48,7 @@ def yes_or_no_formatter(topic): return "{topic} : {action} (Yes: {yes_votes} | No : {no_votes})\n".format(topic=topic['topic'], yes_votes=topic['yes_votes'], no_votes=topic['no_votes'], action=action), action, topic['topic_short'] def main(): + args = parser.parse_args() print("⬇ Getting movies from Plex") movies = get_movies_and_format() @@ -76,7 +82,7 @@ def main(): # all we need to do now is chuck it in a big ol' json file print("✏ Writing to JSON file") - with open("movies.json", "w") as f: + with open(args.output, "w") as f: f.write(json.dumps(to_write, indent=4)) print("✅ Done!") diff --git a/run-in-docker.py b/run-in-docker.py new file mode 100755 index 0000000..0fa277e --- /dev/null +++ b/run-in-docker.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3.6 -u + +import argparse +import os +import shlex +import shutil +import subprocess +import sys + +""" +Helper script to run DoesTheDogWatchPlex cli commands in a Docker container. +Probably only works on *nix/BSD/macOS. Obviously requires Docker. +""" + +parser = argparse.ArgumentParser(description=__doc__) +parser.add_argument("--tag", default="dtdd-plex", help="Docker image name/tag to use [default: dtdd-plex]") + +subparsers = parser.add_subparsers(help='sub-command help') + +docker_build_parser = subparsers.add_parser('docker_build', help='Build Docker image') +docker_build_parser.add_argument("--config", default='', type=str, help="path to config.py to use") +docker_build_parser.add_argument('--force', default=False, type=bool, + help='force using config.py passed in via --config if one already exists (a backup will be created)') + +build_json_parser = subparsers.add_parser('build_json', help='Build the DoesTheDogDie JSON file') +build_json_parser.add_argument('--output', default='output/movies.json', + help='path to write file to [default: output/movies.json]') + +write_to_plex_parser = subparsers.add_parser('write_to_plex', help='Write JSON to plex') +write_to_plex_parser.add_argument('--json-path', default='output/movies.json', + help='path to JSON file containing movie update data [default: output/movies.json') + + +def _run(cmd): + print(f'[DEBUG] Running "{cmd}"') + """ convenience fn to write command as string """ + for line in _myexec(shlex.split(cmd)): + print(line, end='') + +def _myexec(cmd): + """ + runs command yielding each line of stdout as generated allowing them to be + printed unbuffered. Useful for commands that generate a lot of output like + chef runs or docker image builds + """ + popen = subprocess.Popen(cmd, stdout=subprocess.PIPE, universal_newlines=True) + for stdout_line in iter(popen.stdout.readline, ""): + yield stdout_line + popen.stdout.close() + return_code = popen.wait() + if return_code: + raise subprocess.CalledProcessError(return_code, cmd) + +def docker_build(args): + if not args.config: + print('⚠️ --config not specified, assuming a "config.py" exists in "{os.getcwd()}"') + else: + _setup_config(args.config, force=args.force) + + # setup config + if not os.path.exists(args.config): + print(f'❌ No such config file "{args.config}"') + + cwd = os.getcwd() + config_dir = os.path.dirname(args.config) + if os.getcwd() != os.path.dirname(args.config): + if not os.path.exists('config.py'): + new_path = os.path.join(os.getcwd(), 'config.py') + print('✅ No existing config.py that would get overwritten') + print(f"... Copying '{args.config}' -> '{new_path}'") + shutil.copyfile(args.config, new_path) + elif os.path.exists('config.py') and not args.force: + print('⚠️ A file "config.py" exists locally already and would be overwritten') + print('... --force flag not set so not proceeding') + sys.exit(1) + + + _run(f"docker build -t {args.tag} .") + +def _setup_config(config_path, force=False, default_path='config.py'): + if not os.path.exists(config_path): + print(f'❌ No such config file "{config_path}"') + + if os.getcwd() != os.path.dirname(config_path): + if not os.path.exists(default_path): + new_path = os.path.join(os.getcwd(), default_path) + print(f'✅ No existing {default_path} that would get overwritten') + print(f"... Copying '{config_path}' -> '{new_path}'") + shutil.copyfile(config_path, new_path) + elif os.path.exists(default_path) and not force: + print(f'⚠️ A file "{default_path}" exists locally already and would be overwritten') + print('... "--force" flag not set so not proceeding') + sys.exit(1) + elif os.path.exists(default_path) and force: + print(f'⚠️ A file "{default_path}" exists locally already and will be overwritten') + backup_path = default_path + '.bak' + print(f'... Making backup of "{default_path}" -> "{backup_path}"') + shutil.copyfile(default_path, backup_path) + print(f'... Copying "{config_path}" -> "{default_path}"') + shutil.copyfile(config_path, default_path) + + +def _docker_run(tag, cmd, **opts): + opts_str = '' + if opts: + opts_str = ' '.join([f"{flag} {val}" for flag, val in opts.items()]) + _run(f"docker run {opts_str} {tag} {cmd}") + +def build_json(args): + # make data volume container + data_volume_name = 'json-output-data' + _run(f"docker create -v /data --name {data_volume_name} {args.tag}") + _docker_run(args.tag, "build_json.py --output=/data/movies.json", + **{'--volumes-from': data_volume_name, '--rm': '',}) + + if not os.path.exists(os.path.dirname(args.output)): + os.makedirs(os.path.dirname(args.output)) + + _run(f"docker cp {data_volume_name}:/data/movies.json {args.output}") + _run(f"docker rm -v {data_volume_name}") + +def write_to_plex(args): + if not os.path.exists(args.json_path): + print(f'❌ No such file "{args.json_path}"') + sys.exit(1) + _docker_run(args.tag, f"write_to_plex.py --json-path={args.json_path}", + **{'--rm': '',}) + +# set subparser functions. +for fn in [docker_build, build_json, write_to_plex]: + fn_subparser = getattr(sys.modules[__name__], f"{fn.__name__}_parser") + fn_subparser.set_defaults(func=fn) + +if __name__ == '__main__': + args = parser.parse_args() + try: + args.func + except AttributeError: + parser.print_help() + sys.exit(0) + if not args.func == docker_build: + if not os.path.exists(args.config): + print(f'configuration file "{args.config}" does not exist') + sys.exit(1) + + args.func(args) diff --git a/write_to_plex.py b/write_to_plex.py index 36fb3fa..8c2755d 100644 --- a/write_to_plex.py +++ b/write_to_plex.py @@ -1,16 +1,24 @@ from apis.plex import write_data +import argparse import json from tqdm import tqdm +import config -def get_movies_from_json(): - with open("movies.json", "r") as f: +parser = argparse.ArgumentParser() +parser.add_argument('--json-path', required=True, default='movies.json', + help='Path to JSON file containing movie data [default: movies.json]') + + +def get_movies_from_json(json_path): + with open(json_path, "r") as f: return json.loads(f.read()) if __name__ == "__main__": - print("✏ Writing update values from movies.json to Plex") - for movie in tqdm(get_movies_from_json()): + args = parser.parse_args() + print("✏ Writing update values from {} to Plex".format(args.json_path)) + for movie in tqdm(get_movies_from_json(args.json_path)): write_data(movie) print("✅ All done!")