|
13 | 13 | # See the License for the specific language governing permissions and
|
14 | 14 | # limitations under the License.
|
15 | 15 | import argparse
|
| 16 | +import pty |
| 17 | +import shlex |
| 18 | +import time |
16 | 19 |
|
17 |
| -from paasta_tools.utils import PaastaColors |
| 20 | +from paasta_tools.cli.utils import get_paasta_oapi_api_clustername |
| 21 | +from paasta_tools.cli.utils import get_paasta_oapi_client_with_auth |
| 22 | +from paasta_tools.cli.utils import lazy_choices_completer |
| 23 | +from paasta_tools.paastaapi.model.remote_run_start import RemoteRunStart |
| 24 | +from paasta_tools.paastaapi.model.remote_run_stop import RemoteRunStop |
| 25 | +from paasta_tools.utils import get_username |
| 26 | +from paasta_tools.utils import list_clusters |
| 27 | +from paasta_tools.utils import list_services |
| 28 | +from paasta_tools.utils import load_system_paasta_config |
| 29 | +from paasta_tools.utils import SystemPaastaConfig |
18 | 30 |
|
19 | 31 |
|
20 |
| -def add_subparser(subparsers: argparse._SubParsersAction): |
21 |
| - subparsers.add_parser( |
| 32 | +KUBECTL_CMD_TEMPLATE = ( |
| 33 | + "kubectl-eks-{cluster} --token {token} exec -it -n {namespace} {pod} -- /bin/bash" |
| 34 | +) |
| 35 | + |
| 36 | + |
| 37 | +def paasta_remote_run_start( |
| 38 | + args: argparse.Namespace, |
| 39 | + system_paasta_config: SystemPaastaConfig, |
| 40 | +) -> int: |
| 41 | + client = get_paasta_oapi_client_with_auth( |
| 42 | + cluster=get_paasta_oapi_api_clustername(cluster=args.cluster, is_eks=True), |
| 43 | + system_paasta_config=system_paasta_config, |
| 44 | + ) |
| 45 | + if not client: |
| 46 | + print("Cannot get a paasta-api client") |
| 47 | + return 1 |
| 48 | + |
| 49 | + user = get_username() |
| 50 | + start_response = client.remote_run.remote_run_start( |
| 51 | + args.service, |
| 52 | + args.instance, |
| 53 | + RemoteRunStart( |
| 54 | + user=user, |
| 55 | + interactive=args.interactive, |
| 56 | + recreate=args.recreate, |
| 57 | + max_duration=args.max_duration, |
| 58 | + ), |
| 59 | + ) |
| 60 | + if start_response.status >= 300: |
| 61 | + print(f"Error from PaaSTA APIs while starting job: {start_response.message}") |
| 62 | + return 1 |
| 63 | + |
| 64 | + start_time = time.time() |
| 65 | + while time.time() - start_time < args.timeout: |
| 66 | + poll_response = client.remote_run.remote_run_poll( |
| 67 | + args.service, |
| 68 | + args.instance, |
| 69 | + start_response.job_name, |
| 70 | + ) |
| 71 | + if poll_response.status == 200: |
| 72 | + break |
| 73 | + time.sleep(10) |
| 74 | + else: |
| 75 | + print("Timed out while waiting for job to start") |
| 76 | + return 1 |
| 77 | + |
| 78 | + if not args.interactive: |
| 79 | + print("Successfully started remote-run job") |
| 80 | + return 0 |
| 81 | + |
| 82 | + token_response = client.remote_run.remote_run_token( |
| 83 | + args.service, args.instance, user |
| 84 | + ) |
| 85 | + |
| 86 | + exec_command = KUBECTL_CMD_TEMPLATE.format( |
| 87 | + cluster=args.cluster, |
| 88 | + namespace=poll_response.namespace, |
| 89 | + pod=poll_response.pod_name, |
| 90 | + token=token_response.token, |
| 91 | + ) |
| 92 | + pty.spawn(shlex.split(exec_command)) |
| 93 | + return 0 |
| 94 | + |
| 95 | + |
| 96 | +def paasta_remote_run_stop( |
| 97 | + args: argparse.Namespace, |
| 98 | + system_paasta_config: SystemPaastaConfig, |
| 99 | +) -> int: |
| 100 | + client = get_paasta_oapi_client_with_auth( |
| 101 | + cluster=get_paasta_oapi_api_clustername(cluster=args.cluster, is_eks=True), |
| 102 | + system_paasta_config=system_paasta_config, |
| 103 | + ) |
| 104 | + if not client: |
| 105 | + print("Cannot get a paasta-api client") |
| 106 | + return 1 |
| 107 | + response = client.remote_run.remote_run_stop( |
| 108 | + args.service, args.instance, RemoteRunStop(user=get_username()) |
| 109 | + ) |
| 110 | + print(response.message) |
| 111 | + return 0 if response.status < 300 else 1 |
| 112 | + |
| 113 | + |
| 114 | +def add_common_args_to_parser(parser: argparse.ArgumentParser): |
| 115 | + service_arg = parser.add_argument( |
| 116 | + "-s", |
| 117 | + "--service", |
| 118 | + help="The name of the service you wish to inspect. Required.", |
| 119 | + required=True, |
| 120 | + ) |
| 121 | + service_arg.completer = lazy_choices_completer(list_services) # type: ignore |
| 122 | + parser.add_argument( |
| 123 | + "-i", |
| 124 | + "--instance", |
| 125 | + help=( |
| 126 | + "Simulate a docker run for a particular instance of the " |
| 127 | + "service, like 'main' or 'canary'. Required." |
| 128 | + ), |
| 129 | + required=True, |
| 130 | + ) |
| 131 | + cluster_arg = parser.add_argument( |
| 132 | + "-c", |
| 133 | + "--cluster", |
| 134 | + help="The name of the cluster you wish to run your task on. Required.", |
| 135 | + required=True, |
| 136 | + ) |
| 137 | + cluster_arg.completer = lazy_choices_completer(list_clusters) # type: ignore |
| 138 | + |
| 139 | + |
| 140 | +def add_subparser(subparsers: argparse._SubParsersAction) -> None: |
| 141 | + remote_run_parser = subparsers.add_parser( |
22 | 142 | "remote-run",
|
23 |
| - help="Schedule adhoc service sandbox on PaaSTA cluster", |
24 |
| - description=( |
25 |
| - "`paasta remote-run` is useful for running adhoc commands in " |
26 |
| - "context of a service's Docker image." |
| 143 | + help="Run services / jobs remotely", |
| 144 | + description="'paasta remote-run' runs services / jobs remotely", |
| 145 | + formatter_class=argparse.ArgumentDefaultsHelpFormatter, |
| 146 | + ) |
| 147 | + subparsers = remote_run_parser.add_subparsers(dest="remote_run_command") |
| 148 | + start_parser = subparsers.add_parser( |
| 149 | + "start", |
| 150 | + help="Start or connect to a remote-run job", |
| 151 | + description="Starts or connects to a remote-run-job", |
| 152 | + formatter_class=argparse.ArgumentDefaultsHelpFormatter, |
| 153 | + ) |
| 154 | + start_parser.add_argument( |
| 155 | + "-I", |
| 156 | + "--interactive", |
| 157 | + help=( |
| 158 | + "Run container in interactive mode. If interactive is set the " |
| 159 | + 'default command will be "bash" unless otherwise set by the "--cmd" flag' |
| 160 | + ), |
| 161 | + action="store_true", |
| 162 | + default=False, |
| 163 | + ) |
| 164 | + start_parser.add_argument( |
| 165 | + "-m", |
| 166 | + "--max-duration", |
| 167 | + help=( |
| 168 | + "Amount of time in seconds after which the job is " |
| 169 | + "automatically stopped (capped by the API backend)" |
27 | 170 | ),
|
| 171 | + type=int, |
| 172 | + default=1800, |
| 173 | + ) |
| 174 | + start_parser.add_argument( |
| 175 | + "-r", |
| 176 | + "--recreate", |
| 177 | + help="Recreate remote-run job if already existing", |
| 178 | + action="store_true", |
| 179 | + default=False, |
| 180 | + ) |
| 181 | + start_parser.add_argument( |
| 182 | + "-t", |
| 183 | + "--timeout", |
| 184 | + help="Maximum time to wait for a job to start, in seconds", |
| 185 | + type=int, |
| 186 | + default=600, |
| 187 | + ) |
| 188 | + stop_parser = subparsers.add_parser( |
| 189 | + "stop", |
| 190 | + help="Stop your remote-run job if it exists", |
| 191 | + description="Stop your remote-run job if it exists", |
| 192 | + formatter_class=argparse.ArgumentDefaultsHelpFormatter, |
28 | 193 | )
|
| 194 | + add_common_args_to_parser(start_parser) |
| 195 | + add_common_args_to_parser(stop_parser) |
| 196 | + remote_run_parser.set_defaults(command=paasta_remote_run) |
29 | 197 |
|
30 | 198 |
|
31 |
| -def paasta_remote_run(args: argparse.Namespace): |
32 |
| - print(PaastaColors.red("Error: functionality under construction")) |
33 |
| - return 1 |
| 199 | +def paasta_remote_run(args: argparse.Namespace) -> int: |
| 200 | + system_paasta_config = load_system_paasta_config() |
| 201 | + if args.remote_run_command == "start": |
| 202 | + return paasta_remote_run_start(args, system_paasta_config) |
| 203 | + elif args.remote_run_command == "stop": |
| 204 | + return paasta_remote_run_stop(args, system_paasta_config) |
| 205 | + raise ValueError(f"Unsupported subcommand: {args.remote_run_command}") |
0 commit comments