|
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