primitive 0.2.10__py3-none-any.whl → 0.2.12__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- primitive/__about__.py +1 -1
- primitive/agent/actions.py +62 -120
- primitive/agent/commands.py +2 -1
- primitive/agent/runner.py +43 -34
- primitive/agent/uploader.py +2 -2
- primitive/cli.py +2 -0
- primitive/client.py +41 -16
- primitive/daemons/actions.py +48 -62
- primitive/daemons/commands.py +68 -22
- primitive/daemons/launch_agents.py +205 -122
- primitive/daemons/launch_service.py +224 -164
- primitive/daemons/ui.py +41 -0
- primitive/db/base.py +5 -0
- primitive/db/models.py +88 -0
- primitive/db/sqlite.py +34 -0
- primitive/exec/actions.py +0 -1
- primitive/files/actions.py +0 -1
- primitive/hardware/actions.py +11 -10
- primitive/hardware/commands.py +1 -68
- primitive/hardware/ui.py +67 -0
- primitive/monitor/actions.py +199 -0
- primitive/monitor/commands.py +13 -0
- primitive/reservations/actions.py +0 -2
- primitive/utils/auth.py +0 -2
- primitive/utils/daemons.py +54 -0
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/METADATA +3 -1
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/RECORD +30 -22
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/WHEEL +0 -0
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/entry_points.txt +0 -0
- {primitive-0.2.10.dist-info → primitive-0.2.12.dist-info}/licenses/LICENSE.txt +0 -0
    
        primitive/hardware/commands.py
    CHANGED
    
    | @@ -3,13 +3,11 @@ import typing | |
| 3 3 | 
             
            import click
         | 
| 4 4 |  | 
| 5 5 | 
             
            from ..utils.printer import print_result
         | 
| 6 | 
            +
            from .ui import render_hardware_table
         | 
| 6 7 |  | 
| 7 8 | 
             
            if typing.TYPE_CHECKING:
         | 
| 8 9 | 
             
                from ..client import Primitive
         | 
| 9 10 |  | 
| 10 | 
            -
            from rich.console import Console
         | 
| 11 | 
            -
            from rich.table import Table
         | 
| 12 | 
            -
             | 
| 13 11 |  | 
| 14 12 | 
             
            @click.group()
         | 
| 15 13 | 
             
            @click.pass_context
         | 
| @@ -71,71 +69,6 @@ def checkin_command(context): | |
| 71 69 | 
             
                    print_result(message=message, context=context, fg="green")
         | 
| 72 70 |  | 
| 73 71 |  | 
| 74 | 
            -
            def hardware_status_string(hardware):
         | 
| 75 | 
            -
                if activeReservation := hardware.get("activeReservation"):
         | 
| 76 | 
            -
                    if activeReservation.get("status", None) == "in_progress":
         | 
| 77 | 
            -
                        return "Reserved"
         | 
| 78 | 
            -
                if hardware.get("isQuarantined"):
         | 
| 79 | 
            -
                    return "Quarantined"
         | 
| 80 | 
            -
                if not hardware.get("isOnline"):
         | 
| 81 | 
            -
                    return "Offline"
         | 
| 82 | 
            -
                if not hardware.get("isHealthy"):
         | 
| 83 | 
            -
                    return "Not healthy"
         | 
| 84 | 
            -
                if not hardware.get("isAvailable"):
         | 
| 85 | 
            -
                    return "Not available"
         | 
| 86 | 
            -
                else:
         | 
| 87 | 
            -
                    return "Available"
         | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
            def render_hardware_table(hardware_list):
         | 
| 91 | 
            -
                console = Console()
         | 
| 92 | 
            -
             | 
| 93 | 
            -
                table = Table(show_header=True, header_style="bold magenta")
         | 
| 94 | 
            -
                table.add_column("Organization")
         | 
| 95 | 
            -
                table.add_column("Name | Slug")
         | 
| 96 | 
            -
                table.add_column("Status")
         | 
| 97 | 
            -
                table.add_column("Reservation")
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                for hardware in hardware_list:
         | 
| 100 | 
            -
                    name = hardware.get("name")
         | 
| 101 | 
            -
                    slug = hardware.get("slug")
         | 
| 102 | 
            -
                    print_name = name
         | 
| 103 | 
            -
                    if name != slug:
         | 
| 104 | 
            -
                        print_name = f"{name} | {slug}"
         | 
| 105 | 
            -
                    child_table = Table(show_header=False, header_style="bold magenta")
         | 
| 106 | 
            -
                    child_table.add_column("Organization")
         | 
| 107 | 
            -
                    child_table.add_column("Name | Slug")
         | 
| 108 | 
            -
                    child_table.add_column("Status")
         | 
| 109 | 
            -
                    child_table.add_column("Reservation", justify="right")
         | 
| 110 | 
            -
             | 
| 111 | 
            -
                    table.add_row(
         | 
| 112 | 
            -
                        hardware.get("organization").get("name"),
         | 
| 113 | 
            -
                        print_name,
         | 
| 114 | 
            -
                        hardware_status_string(hardware),
         | 
| 115 | 
            -
                        f"{hardware.get('activeReservation').get('createdBy').get('username')} | {hardware.get('activeReservation').get('status')}"
         | 
| 116 | 
            -
                        if hardware.get("activeReservation", None)
         | 
| 117 | 
            -
                        else "",
         | 
| 118 | 
            -
                    )
         | 
| 119 | 
            -
             | 
| 120 | 
            -
                    if len(hardware.get("children", [])) > 0:
         | 
| 121 | 
            -
                        for child in hardware.get("children"):
         | 
| 122 | 
            -
                            name = child.get("name")
         | 
| 123 | 
            -
                            slug = child.get("slug")
         | 
| 124 | 
            -
                            print_name = name
         | 
| 125 | 
            -
                            if name != slug:
         | 
| 126 | 
            -
                                print_name = f"└── {name} | {slug}"
         | 
| 127 | 
            -
                            table.add_row(
         | 
| 128 | 
            -
                                hardware.get("organization").get("name"),
         | 
| 129 | 
            -
                                print_name,
         | 
| 130 | 
            -
                                hardware_status_string(hardware),
         | 
| 131 | 
            -
                                f"{hardware.get('activeReservation').get('createdBy').get('username')} | {hardware.get('activeReservation').get('status')}"
         | 
| 132 | 
            -
                                if hardware.get("activeReservation", None)
         | 
| 133 | 
            -
                                else "",
         | 
| 134 | 
            -
                            )
         | 
| 135 | 
            -
             | 
| 136 | 
            -
                console.print(table)
         | 
| 137 | 
            -
             | 
| 138 | 
            -
             | 
| 139 72 | 
             
            @cli.command("list")
         | 
| 140 73 | 
             
            @click.pass_context
         | 
| 141 74 | 
             
            def list_command(context):
         | 
    
        primitive/hardware/ui.py
    ADDED
    
    | @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            from rich.console import Console
         | 
| 2 | 
            +
            from rich.table import Table
         | 
| 3 | 
            +
             | 
| 4 | 
            +
             | 
| 5 | 
            +
            def render_hardware_table(hardware_list) -> None:
         | 
| 6 | 
            +
                console = Console()
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                table = Table(show_header=True, header_style="bold #FFA800")
         | 
| 9 | 
            +
                table.add_column("Organization")
         | 
| 10 | 
            +
                table.add_column("Name | Slug")
         | 
| 11 | 
            +
                table.add_column("Status")
         | 
| 12 | 
            +
                table.add_column("Reservation")
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                for hardware in hardware_list:
         | 
| 15 | 
            +
                    name = hardware.get("name")
         | 
| 16 | 
            +
                    slug = hardware.get("slug")
         | 
| 17 | 
            +
                    print_name = name
         | 
| 18 | 
            +
                    if name != slug:
         | 
| 19 | 
            +
                        print_name = f"{name} | {slug}"
         | 
| 20 | 
            +
                    child_table = Table(show_header=False, header_style="bold #FFA800")
         | 
| 21 | 
            +
                    child_table.add_column("Organization")
         | 
| 22 | 
            +
                    child_table.add_column("Name | Slug")
         | 
| 23 | 
            +
                    child_table.add_column("Status")
         | 
| 24 | 
            +
                    child_table.add_column("Reservation", justify="right")
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    table.add_row(
         | 
| 27 | 
            +
                        hardware.get("organization").get("name"),
         | 
| 28 | 
            +
                        print_name,
         | 
| 29 | 
            +
                        hardware_status_string(hardware),
         | 
| 30 | 
            +
                        f"{hardware.get('activeReservation').get('createdBy').get('username')} | {hardware.get('activeReservation').get('status')}"
         | 
| 31 | 
            +
                        if hardware.get("activeReservation", None)
         | 
| 32 | 
            +
                        else "",
         | 
| 33 | 
            +
                    )
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    if len(hardware.get("children", [])) > 0:
         | 
| 36 | 
            +
                        for child in hardware.get("children"):
         | 
| 37 | 
            +
                            name = child.get("name")
         | 
| 38 | 
            +
                            slug = child.get("slug")
         | 
| 39 | 
            +
                            print_name = name
         | 
| 40 | 
            +
                            if name != slug:
         | 
| 41 | 
            +
                                print_name = f"└── {name} | {slug}"
         | 
| 42 | 
            +
                            table.add_row(
         | 
| 43 | 
            +
                                hardware.get("organization").get("name"),
         | 
| 44 | 
            +
                                print_name,
         | 
| 45 | 
            +
                                hardware_status_string(hardware),
         | 
| 46 | 
            +
                                f"{hardware.get('activeReservation').get('createdBy').get('username')} | {hardware.get('activeReservation').get('status')}"
         | 
| 47 | 
            +
                                if hardware.get("activeReservation", None)
         | 
| 48 | 
            +
                                else "",
         | 
| 49 | 
            +
                            )
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                console.print(table)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
             | 
| 54 | 
            +
            def hardware_status_string(hardware) -> str:
         | 
| 55 | 
            +
                if activeReservation := hardware.get("activeReservation"):
         | 
| 56 | 
            +
                    if activeReservation.get("status", None) == "in_progress":
         | 
| 57 | 
            +
                        return "Reserved"
         | 
| 58 | 
            +
                if hardware.get("isQuarantined"):
         | 
| 59 | 
            +
                    return "Quarantined"
         | 
| 60 | 
            +
                if not hardware.get("isOnline"):
         | 
| 61 | 
            +
                    return "Offline"
         | 
| 62 | 
            +
                if not hardware.get("isHealthy"):
         | 
| 63 | 
            +
                    return "Not healthy"
         | 
| 64 | 
            +
                if not hardware.get("isAvailable"):
         | 
| 65 | 
            +
                    return "Not available"
         | 
| 66 | 
            +
                else:
         | 
| 67 | 
            +
                    return "Available"
         | 
| @@ -0,0 +1,199 @@ | |
| 1 | 
            +
            import sys
         | 
| 2 | 
            +
            from time import sleep
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            import psutil
         | 
| 5 | 
            +
            from loguru import logger
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            from primitive.__about__ import __version__
         | 
| 8 | 
            +
            from primitive.db import sqlite
         | 
| 9 | 
            +
            from primitive.db.models import JobRun
         | 
| 10 | 
            +
            from primitive.utils.actions import BaseAction
         | 
| 11 | 
            +
            from primitive.utils.exceptions import P_CLI_100
         | 
| 12 | 
            +
             | 
| 13 | 
            +
             | 
| 14 | 
            +
            class Monitor(BaseAction):
         | 
| 15 | 
            +
                def start(self):
         | 
| 16 | 
            +
                    logger.remove()
         | 
| 17 | 
            +
                    logger.add(
         | 
| 18 | 
            +
                        sink=sys.stderr,
         | 
| 19 | 
            +
                        format="<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <level>{message}</level>",
         | 
| 20 | 
            +
                        backtrace=True,
         | 
| 21 | 
            +
                        diagnose=True,
         | 
| 22 | 
            +
                        level="DEBUG" if self.primitive.DEBUG else "INFO",
         | 
| 23 | 
            +
                    )
         | 
| 24 | 
            +
                    logger.info("[*] primitive monitor")
         | 
| 25 | 
            +
                    logger.info(f"[*] Version: {__version__}")
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                    try:
         | 
| 28 | 
            +
                        # hey stupid:
         | 
| 29 | 
            +
                        # do not set is_available to True here, it will mess up the reservation logic
         | 
| 30 | 
            +
                        # only set is_available after we've checked that no active reservation is present
         | 
| 31 | 
            +
                        # setting is_available of the parent also effects the children,
         | 
| 32 | 
            +
                        # which may have active reservations as well
         | 
| 33 | 
            +
                        self.primitive.hardware.check_in_http(is_online=True)
         | 
| 34 | 
            +
                    except Exception as exception:
         | 
| 35 | 
            +
                        logger.exception(f"Error checking in hardware: {exception}")
         | 
| 36 | 
            +
                        sys.exit(1)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    # Initialize the database
         | 
| 39 | 
            +
                    sqlite.init()
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    try:
         | 
| 42 | 
            +
                        active_reservation_id = None
         | 
| 43 | 
            +
                        active_reservation_pk = None
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                        while True:
         | 
| 46 | 
            +
                            # FIRST, check for jobs in the database that are running
         | 
| 47 | 
            +
                            db_job_runs = JobRun.objects.all()
         | 
| 48 | 
            +
                            for job_run in db_job_runs:
         | 
| 49 | 
            +
                                if job_run.pid is None:
         | 
| 50 | 
            +
                                    pid_sleep_amount = 0.1
         | 
| 51 | 
            +
                                    logger.debug(
         | 
| 52 | 
            +
                                        f"Job run {job_run.job_run_id} has no PID. Agent has not started."
         | 
| 53 | 
            +
                                    )
         | 
| 54 | 
            +
                                    logger.debug(
         | 
| 55 | 
            +
                                        f"Sleeping {pid_sleep_amount} seconds before checking again..."
         | 
| 56 | 
            +
                                    )
         | 
| 57 | 
            +
                                    sleep(pid_sleep_amount)
         | 
| 58 | 
            +
                                    continue
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                                logger.debug(
         | 
| 61 | 
            +
                                    f"Checking process PID {job_run.pid} for JobRun {job_run.job_run_id}..."
         | 
| 62 | 
            +
                                )
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                                status = self.primitive.jobs.get_job_status(job_run.job_run_id)
         | 
| 65 | 
            +
                                if status is None or status.data is None:
         | 
| 66 | 
            +
                                    logger.error(
         | 
| 67 | 
            +
                                        f"Error fetching status of <JobRun {job_run.job_run_id}>."
         | 
| 68 | 
            +
                                    )
         | 
| 69 | 
            +
                                    continue
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                                status_value = status.data["jobRun"]["status"]
         | 
| 72 | 
            +
                                conclusion_value = status.data["jobRun"]["conclusion"]
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                                logger.debug(f"- Status: {status_value}")
         | 
| 75 | 
            +
                                logger.debug(f"- Conclusion: {conclusion_value}")
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                                try:
         | 
| 78 | 
            +
                                    parent = psutil.Process(job_run.pid)
         | 
| 79 | 
            +
                                except psutil.NoSuchProcess:
         | 
| 80 | 
            +
                                    logger.debug("Process not found")
         | 
| 81 | 
            +
                                    continue
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                                children = parent.children(recursive=True)
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                                if status_value == "completed" and conclusion_value == "cancelled":
         | 
| 86 | 
            +
                                    logger.warning("Job cancelled by user")
         | 
| 87 | 
            +
                                    for child in children:
         | 
| 88 | 
            +
                                        logger.debug(f"Killing child process {child.pid}...")
         | 
| 89 | 
            +
                                        child.kill()
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                                    logger.debug(f"Killing parent process {parent.pid}...")
         | 
| 92 | 
            +
                                    parent.kill()
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                                if status != "completed":
         | 
| 95 | 
            +
                                    sleep(1)
         | 
| 96 | 
            +
                                    continue
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                            # Second, check for active reservations
         | 
| 99 | 
            +
                            hardware = self.primitive.hardware.get_own_hardware_details()
         | 
| 100 | 
            +
                            if hardware["activeReservation"]:
         | 
| 101 | 
            +
                                if (
         | 
| 102 | 
            +
                                    hardware["activeReservation"]["id"] != active_reservation_id
         | 
| 103 | 
            +
                                    or hardware["activeReservation"]["pk"] != active_reservation_pk
         | 
| 104 | 
            +
                                ):
         | 
| 105 | 
            +
                                    logger.info("New reservation for this hardware.")
         | 
| 106 | 
            +
                                    active_reservation_id = hardware["activeReservation"]["id"]
         | 
| 107 | 
            +
                                    active_reservation_pk = hardware["activeReservation"]["pk"]
         | 
| 108 | 
            +
                                    logger.debug("Active Reservation:")
         | 
| 109 | 
            +
                                    logger.debug(f"Node ID: {active_reservation_id}")
         | 
| 110 | 
            +
                                    logger.debug(f"PK: {active_reservation_pk}")
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                                    logger.debug("Running pre provisioning steps for reservation.")
         | 
| 113 | 
            +
                                    self.primitive.provisioning.add_reservation_authorized_keys(
         | 
| 114 | 
            +
                                        reservation_id=active_reservation_id
         | 
| 115 | 
            +
                                    )
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                            if not active_reservation_id:
         | 
| 118 | 
            +
                                self.primitive.hardware.check_in_http(
         | 
| 119 | 
            +
                                    is_available=True, is_online=True
         | 
| 120 | 
            +
                                )
         | 
| 121 | 
            +
                                logger.debug("Syncing children...")
         | 
| 122 | 
            +
                                self.primitive.hardware._sync_children(hardware=hardware)
         | 
| 123 | 
            +
             | 
| 124 | 
            +
                                sleep_amount = 5
         | 
| 125 | 
            +
                                logger.debug(
         | 
| 126 | 
            +
                                    f"No active reservation found... [sleeping {sleep_amount} seconds]"
         | 
| 127 | 
            +
                                )
         | 
| 128 | 
            +
                                sleep(sleep_amount)
         | 
| 129 | 
            +
                                continue
         | 
| 130 | 
            +
                            else:
         | 
| 131 | 
            +
                                if (
         | 
| 132 | 
            +
                                    hardware["activeReservation"] is None
         | 
| 133 | 
            +
                                    and active_reservation_id is not None
         | 
| 134 | 
            +
                                    # and hardware["isAvailable"] NOTE: this condition was causing the CLI to get into a loop searching for job runs
         | 
| 135 | 
            +
                                ):
         | 
| 136 | 
            +
                                    logger.debug("Previous Reservation is Complete:")
         | 
| 137 | 
            +
                                    logger.debug(f"Node ID: {active_reservation_id}")
         | 
| 138 | 
            +
                                    logger.debug(f"PK: {active_reservation_pk}")
         | 
| 139 | 
            +
                                    logger.debug(
         | 
| 140 | 
            +
                                        "Running cleanup provisioning steps for reservation."
         | 
| 141 | 
            +
                                    )
         | 
| 142 | 
            +
                                    self.primitive.provisioning.remove_reservation_authorized_keys(
         | 
| 143 | 
            +
                                        reservation_id=active_reservation_id
         | 
| 144 | 
            +
                                    )
         | 
| 145 | 
            +
                                    active_reservation_id = None
         | 
| 146 | 
            +
                                    active_reservation_pk = None
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                            # Third, see if the active reservation has any pending job runs
         | 
| 149 | 
            +
                            job_runs_for_reservation = self.primitive.jobs.get_job_runs(
         | 
| 150 | 
            +
                                status="pending", first=1, reservation_id=active_reservation_id
         | 
| 151 | 
            +
                            )
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                            if (
         | 
| 154 | 
            +
                                job_runs_for_reservation is None
         | 
| 155 | 
            +
                                or job_runs_for_reservation.data is None
         | 
| 156 | 
            +
                            ):
         | 
| 157 | 
            +
                                logger.error("Error fetching job runs.")
         | 
| 158 | 
            +
                                sleep_amount = 5
         | 
| 159 | 
            +
                                logger.debug(
         | 
| 160 | 
            +
                                    f"Error fetching job runs... [sleeping {sleep_amount} seconds]"
         | 
| 161 | 
            +
                                )
         | 
| 162 | 
            +
                                sleep(sleep_amount)
         | 
| 163 | 
            +
                                continue
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                            pending_job_runs = [
         | 
| 166 | 
            +
                                edge["node"]
         | 
| 167 | 
            +
                                for edge in job_runs_for_reservation.data["jobRuns"]["edges"]
         | 
| 168 | 
            +
                            ]
         | 
| 169 | 
            +
             | 
| 170 | 
            +
                            if not pending_job_runs:
         | 
| 171 | 
            +
                                self.primitive.hardware.check_in_http(
         | 
| 172 | 
            +
                                    is_available=False, is_online=True
         | 
| 173 | 
            +
                                )
         | 
| 174 | 
            +
                                sleep_amount = 5
         | 
| 175 | 
            +
                                logger.debug(
         | 
| 176 | 
            +
                                    f"Waiting for Job Runs... [sleeping {sleep_amount} seconds]"
         | 
| 177 | 
            +
                                )
         | 
| 178 | 
            +
                                sleep(sleep_amount)
         | 
| 179 | 
            +
                                continue
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                            # If we did find a pending job run, check if it exists in the database
         | 
| 182 | 
            +
                            # and create it if it doesn't.
         | 
| 183 | 
            +
                            # This will trigger the agent to start the job run.
         | 
| 184 | 
            +
                            job_run = pending_job_runs[0]
         | 
| 185 | 
            +
                            if not JobRun.objects.filter_by(job_run_id=job_run["id"]).exists():
         | 
| 186 | 
            +
                                JobRun.objects.create(job_run_id=job_run["id"], pid=None)
         | 
| 187 | 
            +
                                logger.debug(f"Creating job run in database: {job_run['id']}")
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                    except KeyboardInterrupt:
         | 
| 190 | 
            +
                        logger.info("[*] Stopping primitive monitor...")
         | 
| 191 | 
            +
                        try:
         | 
| 192 | 
            +
                            self.primitive.hardware.check_in_http(
         | 
| 193 | 
            +
                                is_available=False, is_online=False, stopping_agent=True
         | 
| 194 | 
            +
                            )
         | 
| 195 | 
            +
             | 
| 196 | 
            +
                        except P_CLI_100 as exception:
         | 
| 197 | 
            +
                            logger.error("[*] Error stopping primitive monitor.")
         | 
| 198 | 
            +
                            logger.error(str(exception))
         | 
| 199 | 
            +
                        sys.exit()
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            import click
         | 
| 2 | 
            +
            import typing
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            if typing.TYPE_CHECKING:
         | 
| 5 | 
            +
                from ..client import Primitive
         | 
| 6 | 
            +
             | 
| 7 | 
            +
             | 
| 8 | 
            +
            @click.command("monitor")
         | 
| 9 | 
            +
            @click.pass_context
         | 
| 10 | 
            +
            def cli(context):
         | 
| 11 | 
            +
                """monitor"""
         | 
| 12 | 
            +
                primitive: Primitive = context.obj.get("PRIMITIVE")
         | 
| 13 | 
            +
                primitive.monitor.start()
         | 
| @@ -89,7 +89,6 @@ class Reservations(BaseAction): | |
| 89 89 | 
             
                    )
         | 
| 90 90 | 
             
                    if messages := result.data.get("reservationCreate").get("messages"):
         | 
| 91 91 | 
             
                        for message in messages:
         | 
| 92 | 
            -
                            logger.enable("primitive")
         | 
| 93 92 | 
             
                            if message.get("kind") == "ERROR":
         | 
| 94 93 | 
             
                                logger.error(message.get("message"))
         | 
| 95 94 | 
             
                            else:
         | 
| @@ -141,7 +140,6 @@ class Reservations(BaseAction): | |
| 141 140 | 
             
                    reservation = reservation_result.data["reservation"]
         | 
| 142 141 | 
             
                    current_status = reservation["status"]
         | 
| 143 142 |  | 
| 144 | 
            -
                    logger.enable("primitive")
         | 
| 145 143 | 
             
                    logger.debug(
         | 
| 146 144 | 
             
                        f"Waiting {total_sleep_time}s for reservation {reservation_id} to be in_progress."
         | 
| 147 145 | 
             
                    )
         | 
    
        primitive/utils/auth.py
    CHANGED
    
    | @@ -11,7 +11,6 @@ MAX_TIME_FOR_BACKOFF = 60 * 15  # 15 minutes | |
| 11 11 |  | 
| 12 12 |  | 
| 13 13 | 
             
            def connection_backoff_handler(details):
         | 
| 14 | 
            -
                logger.enable("primitive")
         | 
| 15 14 | 
             
                logger.error(
         | 
| 16 15 | 
             
                    "Cannot connect to API. Waiting {wait:0.1f} seconds after {tries} tries.".format(
         | 
| 17 16 | 
             
                        **details
         | 
| @@ -25,7 +24,6 @@ def create_new_session(primitive): | |
| 25 24 | 
             
                fingerprint = primitive.host_config.get("fingerprint")
         | 
| 26 25 |  | 
| 27 26 | 
             
                if not token or not transport:
         | 
| 28 | 
            -
                    logger.enable("primitive")
         | 
| 29 27 | 
             
                    logger.error(
         | 
| 30 28 | 
             
                        "CLI is not configured. Run `primitive config` to add an auth token."
         | 
| 31 29 | 
             
                    )
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            from pathlib import Path
         | 
| 2 | 
            +
            from abc import ABC, abstractmethod
         | 
| 3 | 
            +
             | 
| 4 | 
            +
             | 
| 5 | 
            +
            class Daemon(ABC):
         | 
| 6 | 
            +
                name: str
         | 
| 7 | 
            +
                label: str
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                @property
         | 
| 10 | 
            +
                @abstractmethod
         | 
| 11 | 
            +
                def logs(self) -> Path:
         | 
| 12 | 
            +
                    """Path to to agent or service logs"""
         | 
| 13 | 
            +
                    pass
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                @property
         | 
| 16 | 
            +
                @abstractmethod
         | 
| 17 | 
            +
                def file_path(self) -> Path:
         | 
| 18 | 
            +
                    """Path to agent or service definition file"""
         | 
| 19 | 
            +
                    pass
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                @abstractmethod
         | 
| 22 | 
            +
                def install(self) -> bool:
         | 
| 23 | 
            +
                    """Install the daemon"""
         | 
| 24 | 
            +
                    pass
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                @abstractmethod
         | 
| 27 | 
            +
                def uninstall(self) -> bool:
         | 
| 28 | 
            +
                    """Uninstall the daemon"""
         | 
| 29 | 
            +
                    pass
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                @abstractmethod
         | 
| 32 | 
            +
                def start(self) -> bool:
         | 
| 33 | 
            +
                    """Start the daemon"""
         | 
| 34 | 
            +
                    pass
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                @abstractmethod
         | 
| 37 | 
            +
                def stop(self) -> bool:
         | 
| 38 | 
            +
                    """Stop the daemon"""
         | 
| 39 | 
            +
                    pass
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                @abstractmethod
         | 
| 42 | 
            +
                def is_installed(self) -> bool:
         | 
| 43 | 
            +
                    """Check if the daemon is installed"""
         | 
| 44 | 
            +
                    pass
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                @abstractmethod
         | 
| 47 | 
            +
                def is_active(self) -> bool:
         | 
| 48 | 
            +
                    """Check if the daemon is active"""
         | 
| 49 | 
            +
                    pass
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                @abstractmethod
         | 
| 52 | 
            +
                def view_logs(self) -> None:
         | 
| 53 | 
            +
                    """View the daemon logs"""
         | 
| 54 | 
            +
                    pass
         | 
| @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            Metadata-Version: 2.4
         | 
| 2 2 | 
             
            Name: primitive
         | 
| 3 | 
            -
            Version: 0.2. | 
| 3 | 
            +
            Version: 0.2.12
         | 
| 4 4 | 
             
            Project-URL: Documentation, https://github.com//primitivecorp/primitive-cli#readme
         | 
| 5 5 | 
             
            Project-URL: Issues, https://github.com//primitivecorp/primitive-cli/issues
         | 
| 6 6 | 
             
            Project-URL: Source, https://github.com//primitivecorp/primitive-cli
         | 
| @@ -23,9 +23,11 @@ Requires-Dist: gql[all] | |
| 23 23 | 
             
            Requires-Dist: loguru
         | 
| 24 24 | 
             
            Requires-Dist: paramiko[invoke]
         | 
| 25 25 | 
             
            Requires-Dist: primitive-pal==0.1.4
         | 
| 26 | 
            +
            Requires-Dist: psutil>=7.0.0
         | 
| 26 27 | 
             
            Requires-Dist: pyyaml
         | 
| 27 28 | 
             
            Requires-Dist: rich>=13.9.4
         | 
| 28 29 | 
             
            Requires-Dist: speedtest-cli
         | 
| 30 | 
            +
            Requires-Dist: sqlalchemy>=2.0.40
         | 
| 29 31 | 
             
            Description-Content-Type: text/markdown
         | 
| 30 32 |  | 
| 31 33 | 
             
            # primitive
         | 
| @@ -1,28 +1,32 @@ | |
| 1 | 
            -
            primitive/__about__.py,sha256= | 
| 1 | 
            +
            primitive/__about__.py,sha256=ZJWGMrU4ZWd5v5sybxqbMIUQbxi44Ln1dT1SjERj8jk,130
         | 
| 2 2 | 
             
            primitive/__init__.py,sha256=bwKdgggKNVssJFVPfKSxqFMz4IxSr54WWbmiZqTMPNI,106
         | 
| 3 | 
            -
            primitive/cli.py,sha256= | 
| 4 | 
            -
            primitive/client.py,sha256= | 
| 3 | 
            +
            primitive/cli.py,sha256=g7EtHI9MATAB0qQu5w-WzbXtxz_8zu8z5E7sETmMkKU,2509
         | 
| 4 | 
            +
            primitive/client.py,sha256=h8WZVnQylVe0vbpuyC8YZHl2JyITSPC-1HbUcmrE5pc,3623
         | 
| 5 5 | 
             
            primitive/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 6 | 
            -
            primitive/agent/actions.py,sha256= | 
| 7 | 
            -
            primitive/agent/commands.py,sha256 | 
| 8 | 
            -
            primitive/agent/runner.py,sha256= | 
| 9 | 
            -
            primitive/agent/uploader.py,sha256= | 
| 6 | 
            +
            primitive/agent/actions.py,sha256=PzFOgxuRrhbUGpUygnTZVru58Fv87GI8tgJYkZB1LjI,3773
         | 
| 7 | 
            +
            primitive/agent/commands.py,sha256=cK7d3OcN5Z65gQWVZFQ-Y9ddw9Pes4f9OVBpeMsj5sE,255
         | 
| 8 | 
            +
            primitive/agent/runner.py,sha256=CoRyReO3jPV8B7vILVWdszFD4GVop7HsVEUo1hoRXjo,14556
         | 
| 9 | 
            +
            primitive/agent/uploader.py,sha256=ZzrzsajNBogwEC7mT6Ejy0h2Jd9axMYGzt9pbCvVMlk,3171
         | 
| 10 10 | 
             
            primitive/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 11 11 | 
             
            primitive/auth/actions.py,sha256=MPsG9LcKcOPwA7gZ9Ewk0PZJhTQvIrGfODdz4GxSzgA,999
         | 
| 12 12 | 
             
            primitive/auth/commands.py,sha256=2z5u5xX64n0yILucx9emtWh3uQXLvs2QQQQIldZGr94,2341
         | 
| 13 13 | 
             
            primitive/auth/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 14 14 | 
             
            primitive/auth/graphql/queries.py,sha256=jhrr_VFzHIn8vcVprMIzUx7V4kkWYdR6CKMKPoVFv60,180
         | 
| 15 15 | 
             
            primitive/daemons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 16 | 
            -
            primitive/daemons/actions.py,sha256= | 
| 17 | 
            -
            primitive/daemons/commands.py,sha256 | 
| 18 | 
            -
            primitive/daemons/launch_agents.py,sha256= | 
| 19 | 
            -
            primitive/daemons/launch_service.py,sha256= | 
| 16 | 
            +
            primitive/daemons/actions.py,sha256=V4BUCLS8UoQOZoS2vwEkYQpWAUNdZnMPBhQR19RvQXs,2023
         | 
| 17 | 
            +
            primitive/daemons/commands.py,sha256=Xt4qFymNrDLdHJhRnEH_4Re-2xX6w1OT-chV9k7dFCs,2670
         | 
| 18 | 
            +
            primitive/daemons/launch_agents.py,sha256=KD7cqQZDtfDmMyNiYrswTRWEktvS9A1QsqQF1jhMDjw,7940
         | 
| 19 | 
            +
            primitive/daemons/launch_service.py,sha256=IhvKZqU5juA3hKsvUB2275BP8lNBl5XWkVoqVgdwy-o,8013
         | 
| 20 | 
            +
            primitive/daemons/ui.py,sha256=Af3OJWJ0jdGlb1nfA5yaGYdhBEqqpM8zP2U2vUQdCbw,1236
         | 
| 21 | 
            +
            primitive/db/base.py,sha256=mH7f2d_jiyxJSSx9Gk53QBXRa3LiKBsBjkFgvmtH1WA,83
         | 
| 22 | 
            +
            primitive/db/models.py,sha256=GfnJdAq4Tb68CI4BKAuJDZVqioGavveaAHbCPeLNngw,2840
         | 
| 23 | 
            +
            primitive/db/sqlite.py,sha256=3V9ZxbgME1ThfJp90MPLUxU8b9imgNZM5CHOnA-WkaQ,953
         | 
| 20 24 | 
             
            primitive/exec/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 21 | 
            -
            primitive/exec/actions.py,sha256= | 
| 25 | 
            +
            primitive/exec/actions.py,sha256=4d_TCjNDcVFoZ9Zw7ZuBa6hKMv2Xzm7_UX_8wcX1aSk,4124
         | 
| 22 26 | 
             
            primitive/exec/commands.py,sha256=66LO2kkJC-ynNZQpUCXv4Ol15QoacdSZAHblePDcmLo,510
         | 
| 23 27 | 
             
            primitive/exec/interactive.py,sha256=TscY6s2ZysijidKPheq6y-fCErUVLS0zcdTW8XyFWGI,2435
         | 
| 24 28 | 
             
            primitive/files/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 25 | 
            -
            primitive/files/actions.py,sha256= | 
| 29 | 
            +
            primitive/files/actions.py,sha256=jvsBivYBmPeqb6Ge7gECm_x20AFUL7UYPGJJFmoCeOM,12409
         | 
| 26 30 | 
             
            primitive/files/commands.py,sha256=ZNW4y8JZF1li7P5ej1r-Xcqu0iGpRRlMYvthuZOLLbQ,1163
         | 
| 27 31 | 
             
            primitive/files/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 28 32 | 
             
            primitive/files/graphql/fragments.py,sha256=II6WHZjzSqX4IELwdiWokqHTKvDq6mMHF5gp3rLnj3U,231
         | 
| @@ -38,9 +42,10 @@ primitive/graphql/relay.py,sha256=bmij2AjdpURQ6GGVCxwWhauF-r_SxuAU2oJ4sDbLxpI,72 | |
| 38 42 | 
             
            primitive/graphql/sdk.py,sha256=KhVWDZms_eMBgt6ftSJitRALguagy-nmrj4IC2taeXY,1535
         | 
| 39 43 | 
             
            primitive/graphql/utility_fragments.py,sha256=uIjwILC4QtWNyO5vu77VjQf_p0jvP3A9q_6zRq91zqs,303
         | 
| 40 44 | 
             
            primitive/hardware/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 41 | 
            -
            primitive/hardware/actions.py,sha256= | 
| 45 | 
            +
            primitive/hardware/actions.py,sha256=d5KwuSsceOhDH9rgOL7YTCpQPhqT2inRTiZnROtiDic,26076
         | 
| 42 46 | 
             
            primitive/hardware/android.py,sha256=tu7pBPxWFrIwb_mm5CEdFFf1_veNDOKjOCQg13i_Lh4,2758
         | 
| 43 | 
            -
            primitive/hardware/commands.py,sha256= | 
| 47 | 
            +
            primitive/hardware/commands.py,sha256=ixMPhDOpsU-eONxmimqKVynus-Eaq2XPKEK017WM_rM,3229
         | 
| 48 | 
            +
            primitive/hardware/ui.py,sha256=12rucuZ2s-w5R4bKyxON5dEbrdDnVf5sbj3K_nbdo44,2473
         | 
| 44 49 | 
             
            primitive/hardware/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 45 50 | 
             
            primitive/hardware/graphql/fragments.py,sha256=kI6qnTNjaEaUr-C6eD55COphtueVYbYOWZwN5EW_3qw,350
         | 
| 46 51 | 
             
            primitive/hardware/graphql/mutations.py,sha256=_4Hkbfik9Ron4T-meulu6T-9FR_BZjyPNwn745MPksU,1484
         | 
| @@ -52,6 +57,8 @@ primitive/jobs/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3 | |
| 52 57 | 
             
            primitive/jobs/graphql/fragments.py,sha256=1_ZttT7dx36KDC3DClJz9M8LMpsPwXySBygHSiUEcGg,619
         | 
| 53 58 | 
             
            primitive/jobs/graphql/mutations.py,sha256=8ASvCmwQh7cMeeiykOdYaYVryG8FRIuVF6v_J8JJZuw,219
         | 
| 54 59 | 
             
            primitive/jobs/graphql/queries.py,sha256=BrU_GnLjK0bTAmWsLSmGEUea7EM8MqTKxN1Qp6sSjwc,1597
         | 
| 60 | 
            +
            primitive/monitor/actions.py,sha256=GUQrwuan82pOJ5gI2FvQYzgDoP4fs28PdcI_fg_aXRs,8692
         | 
| 61 | 
            +
            primitive/monitor/commands.py,sha256=dZsD8WKGU4OYO_AlKawfeRNVTMN0xJ-DFRkmKTS464s,258
         | 
| 55 62 | 
             
            primitive/organizations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 56 63 | 
             
            primitive/organizations/actions.py,sha256=Tgp_rox0jcvfhQ-LmcWc9vkPdeJu5Bk6U1rNuT9oDnw,1088
         | 
| 57 64 | 
             
            primitive/organizations/commands.py,sha256=_dwgVEJCqMa5VgB_7P1wLPFc0AuT1p9dtyR9JRr4kpw,487
         | 
| @@ -71,7 +78,7 @@ primitive/provisioning/actions.py,sha256=IYZYAbtomtZtlkqDaBxx4e7PFKGkRNqek_tABH6 | |
| 71 78 | 
             
            primitive/provisioning/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 72 79 | 
             
            primitive/provisioning/graphql/queries.py,sha256=cBtuKa6shoatYZfKSnQoPJP6B8g8y3QhFqJ_pkvMcG0,134
         | 
| 73 80 | 
             
            primitive/reservations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 74 | 
            -
            primitive/reservations/actions.py,sha256= | 
| 81 | 
            +
            primitive/reservations/actions.py,sha256=FiodRTVUgGgFfoksnN9W0XNdGTd2AxPJTfUrZbmQ0_g,6179
         | 
| 75 82 | 
             
            primitive/reservations/commands.py,sha256=LFRoV59QGgWIjBdrGjJdffHugg8TLe0Fwlcyu_JaTkk,2369
         | 
| 76 83 | 
             
            primitive/reservations/graphql/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 77 84 | 
             
            primitive/reservations/graphql/fragments.py,sha256=o5JXkhrFrftYZbsmOQRj105bNw4WwO6U34yN0X-pbCg,411
         | 
| @@ -79,17 +86,18 @@ primitive/reservations/graphql/mutations.py,sha256=IqzwQL7OclN7RpIcidrTQo9cGYofY | |
| 79 86 | 
             
            primitive/reservations/graphql/queries.py,sha256=x31wTRelskX2fc0fx2qrY7XT1q74nvzLv_Xef3o9weg,746
         | 
| 80 87 | 
             
            primitive/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
         | 
| 81 88 | 
             
            primitive/utils/actions.py,sha256=HOFrmM3-0A_A3NS84MqrZ6JmQEiiPSoDqEeuu6b_qfQ,196
         | 
| 82 | 
            -
            primitive/utils/auth.py,sha256= | 
| 89 | 
            +
            primitive/utils/auth.py,sha256=uBIZNPF2CpbaPV2UMi6eWVUKghV6WIm-pG3-UM29bNs,1465
         | 
| 83 90 | 
             
            primitive/utils/cache.py,sha256=FHGmVWYLJFQOazpXXcEwI0YJEZbdkgG39nOLdOv6VNk,1575
         | 
| 84 91 | 
             
            primitive/utils/chunk_size.py,sha256=PAuVuirUTA9oRXyjo1c6MWxo31WVBRkWMuWw-AS58Bw,2914
         | 
| 85 92 | 
             
            primitive/utils/config.py,sha256=DlFM5Nglo22WPtbpZSVtH7NX-PTMaKYlcrUE7GPRG4c,1058
         | 
| 93 | 
            +
            primitive/utils/daemons.py,sha256=YkG-OcrTxMhGedbNJMKLq_e7CdTy30Ba2oCVUY-09Co,1086
         | 
| 86 94 | 
             
            primitive/utils/exceptions.py,sha256=DrYHTcCAJGC7cCUwOx_FmdlVLWRdpzvDvpLb82heppE,311
         | 
| 87 95 | 
             
            primitive/utils/memory_size.py,sha256=4xfha21kW82nFvOTtDFx9Jk2ZQoEhkfXii-PGNTpIUk,3058
         | 
| 88 96 | 
             
            primitive/utils/printer.py,sha256=f1XUpqi5dkTL3GWvYRUGlSwtj2IxU1q745T4Fxo7Tn4,370
         | 
| 89 97 | 
             
            primitive/utils/shell.py,sha256=jWzb7ky7p987dJas6ZvarK3IJNZ5cwBXcryRWb9Uh6U,2072
         | 
| 90 98 | 
             
            primitive/utils/text.py,sha256=XiESMnlhjQ534xE2hMNf08WehE1SKaYFRNih0MmnK0k,829
         | 
| 91 | 
            -
            primitive-0.2. | 
| 92 | 
            -
            primitive-0.2. | 
| 93 | 
            -
            primitive-0.2. | 
| 94 | 
            -
            primitive-0.2. | 
| 95 | 
            -
            primitive-0.2. | 
| 99 | 
            +
            primitive-0.2.12.dist-info/METADATA,sha256=5GebdNRhv_zCe-W5OP_G7YDzDSzGVC1wA55M0NJdDlQ,3733
         | 
| 100 | 
            +
            primitive-0.2.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
         | 
| 101 | 
            +
            primitive-0.2.12.dist-info/entry_points.txt,sha256=p1K8DMCWka5FqLlqP1sPek5Uovy9jq8u51gUsP-z334,48
         | 
| 102 | 
            +
            primitive-0.2.12.dist-info/licenses/LICENSE.txt,sha256=B8kmQMJ2sxYygjCLBk770uacaMci4mPSoJJ8WoDBY_c,1098
         | 
| 103 | 
            +
            primitive-0.2.12.dist-info/RECORD,,
         | 
| 
            File without changes
         | 
| 
            File without changes
         | 
| 
            File without changes
         |