|
13 | 13 |
|
14 | 14 | from tutor import config as tutor_config
|
15 | 15 | from tutor import env, fmt, hooks
|
16 |
| -from tutor.commands.config import save as config_save_command |
17 | 16 | from tutor.commands.context import Context
|
18 | 17 | from tutor.commands.jobs_utils import (
|
19 | 18 | create_user_template,
|
@@ -541,68 +540,57 @@ def do_callback(service_commands: t.Iterable[tuple[str, str]]) -> None:
|
541 | 540 | runner.run_task_from_str(service, command)
|
542 | 541 |
|
543 | 542 |
|
544 |
| -@click.command(help="Install a pip package at runtime") |
545 |
| -@click.pass_context |
546 |
| -@click.argument("package") |
547 |
| -def pip_install(context: click.Context, package: str) -> t.Iterable[tuple[str, str]]: |
| 543 | +@click.command(help="Build all persistent pip packages and upload to MinIO") |
| 544 | +@click.pass_obj |
| 545 | +def build_packages(context: Context) -> t.Iterable[tuple[str, str]]: |
548 | 546 | """
|
549 |
| - Installs a pip package persistently in the lms and cms container and |
550 |
| - restarts with uwsgi server in both containers. |
| 547 | + Build the persistent pip packages and upload to MinIO. |
| 548 | + You need to update the `PERSISTENT_PIP_PACKAGES` variable |
| 549 | + in the config file to add/remove packages. |
551 | 550 | """
|
552 |
| - |
553 |
| - # TODO Only add package to config if pip install is successful |
554 |
| - fmt.echo_info(f"Adding {package} to config...") |
555 |
| - context.invoke( |
556 |
| - config_save_command, |
557 |
| - interactive=False, |
558 |
| - set_vars=[], |
559 |
| - append_vars=[("PERSISTENT_PIP_PACKAGES", package)], |
560 |
| - remove_vars=[], |
561 |
| - unset_vars=[], |
562 |
| - env_only=False, |
563 |
| - clean_env=False, |
| 551 | + config = tutor_config.load(context.root) |
| 552 | + all_packages = " ".join( |
| 553 | + package for package in t.cast(list[str], config["PERSISTENT_PIP_PACKAGES"]) |
564 | 554 | )
|
565 | 555 |
|
566 | 556 | script = f"""
|
567 | 557 | pip install \
|
568 |
| - --prefix=/mnt/persistent-python-packages \ |
569 |
| - {package} \ |
570 |
| - && echo \"$(date)\" > /mnt/persistent-python-packages/.uwsgi_trigger |
571 |
| - """ |
572 |
| - |
573 |
| - yield ("lms", script) |
574 |
| - |
| 558 | + --prefix=/openedx/persistent-python-packages/deps \ |
| 559 | + {all_packages} \ |
| 560 | + && python3 -c ' |
| 561 | +import os, shutil, tempfile, boto3, botocore, datetime, zipfile |
| 562 | +
|
| 563 | +DEPS_DIR = "/openedx/persistent-python-packages/deps" |
| 564 | +MINIO_KEY = "deps.zip" |
| 565 | +DEPS_ZIP_PATH = DEPS_DIR[:-4] + MINIO_KEY |
| 566 | +MINIO_BUCKET = "tutor-deps" |
| 567 | +
|
| 568 | +s3 = boto3.client( |
| 569 | + "s3", |
| 570 | + endpoint_url="http://" + os.environ.get("MINIO_HOST"), |
| 571 | + aws_access_key_id=os.environ.get("OPENEDX_AWS_ACCESS_KEY"), |
| 572 | + aws_secret_access_key=os.environ.get("OPENEDX_AWS_SECRET_ACCESS_KEY"), |
| 573 | +) |
575 | 574 |
|
576 |
| -@click.command(help="Remove a pip package at runtime") |
577 |
| -@click.pass_obj |
578 |
| -@click.pass_context |
579 |
| -@click.argument("package") |
580 |
| -def pip_uninstall( |
581 |
| - click_context: click.Context, context: Context, package: str |
582 |
| -) -> t.Iterable[tuple[str, str]]: |
| 575 | +def _upload_to_minio(local_path): |
| 576 | + try: |
| 577 | + s3.head_bucket(Bucket=MINIO_BUCKET) |
| 578 | + except botocore.exceptions.ClientError: |
| 579 | + s3.create_bucket(Bucket=MINIO_BUCKET) |
| 580 | + s3.upload_file(local_path, MINIO_BUCKET, MINIO_KEY) |
| 581 | + print(f"Uploaded {{local_path}} → MinIO:{{MINIO_BUCKET}}/{{MINIO_KEY}}") |
| 582 | + os.remove(local_path) |
| 583 | +
|
| 584 | +def _make_zip_archive(src_dir): |
| 585 | + with tempfile.TemporaryDirectory(prefix="tutor-depszip-") as zip_dir: |
| 586 | + path = os.path.join(zip_dir, "deps.zip") |
| 587 | + shutil.make_archive(path[:-4], format="zip", root_dir=src_dir) |
| 588 | + shutil.move(path, DEPS_ZIP_PATH) |
| 589 | +
|
| 590 | +_make_zip_archive(DEPS_DIR) |
| 591 | +_upload_to_minio(DEPS_ZIP_PATH) |
| 592 | +' |
583 | 593 | """
|
584 |
| - Deletes the persistently installed pip package along with its dependencies |
585 |
| - """ |
586 |
| - |
587 |
| - fmt.echo_info(f"Removing {package} from config...") |
588 |
| - click_context.invoke( |
589 |
| - config_save_command, |
590 |
| - interactive=False, |
591 |
| - set_vars=[], |
592 |
| - append_vars=[], |
593 |
| - remove_vars=[("PERSISTENT_PIP_PACKAGES", package)], |
594 |
| - unset_vars=[], |
595 |
| - env_only=False, |
596 |
| - clean_env=False, |
597 |
| - ) |
598 |
| - |
599 |
| - script = "rm -rf /mnt/persistent-python-packages/lib/" |
600 |
| - config = tutor_config.load(context.root) |
601 |
| - values = t.cast(list[str], config["PERSISTENT_PIP_PACKAGES"]) |
602 |
| - remaining_packages = " ".join(values) |
603 |
| - if len(values) > 0: |
604 |
| - script += f" && pip install --prefix=/mnt/persistent-python-packages {remaining_packages}" |
605 |
| - script += ' && echo "$(date)" > /mnt/persistent-python-packages/.uwsgi_trigger' |
606 | 594 |
|
607 | 595 | yield ("lms", script)
|
608 | 596 |
|
@@ -631,8 +619,7 @@ def run_migrations(package: str) -> t.Iterable[tuple[str, str]]:
|
631 | 619 | settheme,
|
632 | 620 | sqlshell,
|
633 | 621 | update_mysql_authentication_plugin,
|
634 |
| - pip_install, |
635 |
| - pip_uninstall, |
| 622 | + build_packages, |
636 | 623 | run_migrations,
|
637 | 624 | ]
|
638 | 625 | )
|
0 commit comments