paasta-tools 1.27.0__py3-none-any.whl → 1.35.8__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.

Potentially problematic release.


This version of paasta-tools might be problematic. Click here for more details.

Files changed (192) hide show
  1. paasta_tools/__init__.py +1 -1
  2. paasta_tools/api/api_docs/swagger.json +9 -1
  3. paasta_tools/api/tweens/auth.py +2 -1
  4. paasta_tools/api/views/instance.py +9 -2
  5. paasta_tools/api/views/remote_run.py +2 -0
  6. paasta_tools/async_utils.py +4 -1
  7. paasta_tools/bounce_lib.py +8 -5
  8. paasta_tools/check_services_replication_tools.py +10 -4
  9. paasta_tools/check_spark_jobs.py +1 -1
  10. paasta_tools/cli/cli.py +4 -4
  11. paasta_tools/cli/cmds/autoscale.py +2 -0
  12. paasta_tools/cli/cmds/check.py +2 -0
  13. paasta_tools/cli/cmds/cook_image.py +2 -0
  14. paasta_tools/cli/cmds/get_docker_image.py +2 -0
  15. paasta_tools/cli/cmds/get_image_version.py +2 -0
  16. paasta_tools/cli/cmds/get_latest_deployment.py +2 -0
  17. paasta_tools/cli/cmds/info.py +10 -3
  18. paasta_tools/cli/cmds/itest.py +2 -0
  19. paasta_tools/cli/cmds/list_namespaces.py +2 -0
  20. paasta_tools/cli/cmds/local_run.py +122 -27
  21. paasta_tools/cli/cmds/logs.py +31 -7
  22. paasta_tools/cli/cmds/mark_for_deployment.py +14 -4
  23. paasta_tools/cli/cmds/mesh_status.py +3 -2
  24. paasta_tools/cli/cmds/push_to_registry.py +2 -0
  25. paasta_tools/cli/cmds/remote_run.py +156 -12
  26. paasta_tools/cli/cmds/rollback.py +6 -2
  27. paasta_tools/cli/cmds/secret.py +4 -2
  28. paasta_tools/cli/cmds/security_check.py +2 -0
  29. paasta_tools/cli/cmds/spark_run.py +7 -3
  30. paasta_tools/cli/cmds/status.py +59 -29
  31. paasta_tools/cli/cmds/validate.py +325 -40
  32. paasta_tools/cli/cmds/wait_for_deployment.py +2 -0
  33. paasta_tools/cli/schemas/adhoc_schema.json +3 -0
  34. paasta_tools/cli/schemas/autoscaling_schema.json +3 -2
  35. paasta_tools/cli/schemas/eks_schema.json +24 -1
  36. paasta_tools/cli/schemas/kubernetes_schema.json +1 -0
  37. paasta_tools/cli/schemas/smartstack_schema.json +14 -0
  38. paasta_tools/cli/utils.py +34 -20
  39. paasta_tools/contrib/bounce_log_latency_parser.py +1 -1
  40. paasta_tools/contrib/check_orphans.py +1 -1
  41. paasta_tools/contrib/get_running_task_allocation.py +1 -1
  42. paasta_tools/contrib/ide_helper.py +14 -14
  43. paasta_tools/contrib/mock_patch_checker.py +1 -1
  44. paasta_tools/contrib/paasta_update_soa_memcpu.py +10 -14
  45. paasta_tools/contrib/render_template.py +1 -1
  46. paasta_tools/contrib/shared_ip_check.py +1 -1
  47. paasta_tools/generate_deployments_for_service.py +2 -0
  48. paasta_tools/instance/hpa_metrics_parser.py +3 -5
  49. paasta_tools/instance/kubernetes.py +70 -36
  50. paasta_tools/kubernetes/application/controller_wrappers.py +23 -2
  51. paasta_tools/kubernetes/remote_run.py +52 -25
  52. paasta_tools/kubernetes_tools.py +60 -69
  53. paasta_tools/long_running_service_tools.py +15 -5
  54. paasta_tools/mesos/master.py +1 -1
  55. paasta_tools/metrics/metastatus_lib.py +1 -25
  56. paasta_tools/metrics/metrics_lib.py +12 -3
  57. paasta_tools/paastaapi/__init__.py +1 -1
  58. paasta_tools/paastaapi/api/autoscaler_api.py +1 -1
  59. paasta_tools/paastaapi/api/default_api.py +1 -1
  60. paasta_tools/paastaapi/api/remote_run_api.py +1 -1
  61. paasta_tools/paastaapi/api/resources_api.py +1 -1
  62. paasta_tools/paastaapi/api/service_api.py +1 -1
  63. paasta_tools/paastaapi/api_client.py +1 -1
  64. paasta_tools/paastaapi/configuration.py +2 -2
  65. paasta_tools/paastaapi/exceptions.py +1 -1
  66. paasta_tools/paastaapi/model/adhoc_launch_history.py +1 -1
  67. paasta_tools/paastaapi/model/autoscaler_count_msg.py +1 -1
  68. paasta_tools/paastaapi/model/autoscaling_override.py +1 -1
  69. paasta_tools/paastaapi/model/deploy_queue.py +1 -1
  70. paasta_tools/paastaapi/model/deploy_queue_service_instance.py +1 -1
  71. paasta_tools/paastaapi/model/envoy_backend.py +1 -1
  72. paasta_tools/paastaapi/model/envoy_location.py +1 -1
  73. paasta_tools/paastaapi/model/envoy_status.py +1 -1
  74. paasta_tools/paastaapi/model/flink_cluster_overview.py +1 -1
  75. paasta_tools/paastaapi/model/flink_config.py +1 -1
  76. paasta_tools/paastaapi/model/flink_job.py +1 -1
  77. paasta_tools/paastaapi/model/flink_job_details.py +1 -1
  78. paasta_tools/paastaapi/model/flink_jobs.py +1 -1
  79. paasta_tools/paastaapi/model/float_and_error.py +1 -1
  80. paasta_tools/paastaapi/model/hpa_metric.py +1 -1
  81. paasta_tools/paastaapi/model/inline_object.py +1 -1
  82. paasta_tools/paastaapi/model/inline_response200.py +1 -1
  83. paasta_tools/paastaapi/model/inline_response2001.py +1 -1
  84. paasta_tools/paastaapi/model/inline_response202.py +1 -1
  85. paasta_tools/paastaapi/model/inline_response403.py +1 -1
  86. paasta_tools/paastaapi/model/instance_bounce_status.py +1 -1
  87. paasta_tools/paastaapi/model/instance_mesh_status.py +1 -1
  88. paasta_tools/paastaapi/model/instance_status.py +1 -1
  89. paasta_tools/paastaapi/model/instance_status_adhoc.py +1 -1
  90. paasta_tools/paastaapi/model/instance_status_cassandracluster.py +1 -1
  91. paasta_tools/paastaapi/model/instance_status_flink.py +1 -1
  92. paasta_tools/paastaapi/model/instance_status_kafkacluster.py +1 -1
  93. paasta_tools/paastaapi/model/instance_status_kubernetes.py +1 -1
  94. paasta_tools/paastaapi/model/instance_status_kubernetes_autoscaling_status.py +1 -1
  95. paasta_tools/paastaapi/model/instance_status_kubernetes_v2.py +1 -1
  96. paasta_tools/paastaapi/model/instance_status_tron.py +1 -1
  97. paasta_tools/paastaapi/model/instance_tasks.py +1 -1
  98. paasta_tools/paastaapi/model/integer_and_error.py +1 -1
  99. paasta_tools/paastaapi/model/kubernetes_container.py +1 -1
  100. paasta_tools/paastaapi/model/kubernetes_container_v2.py +1 -1
  101. paasta_tools/paastaapi/model/kubernetes_healthcheck.py +1 -1
  102. paasta_tools/paastaapi/model/kubernetes_pod.py +1 -1
  103. paasta_tools/paastaapi/model/kubernetes_pod_event.py +1 -1
  104. paasta_tools/paastaapi/model/kubernetes_pod_v2.py +1 -1
  105. paasta_tools/paastaapi/model/kubernetes_replica_set.py +1 -1
  106. paasta_tools/paastaapi/model/kubernetes_version.py +4 -1
  107. paasta_tools/paastaapi/model/remote_run_outcome.py +1 -1
  108. paasta_tools/paastaapi/model/remote_run_start.py +4 -1
  109. paasta_tools/paastaapi/model/remote_run_stop.py +1 -1
  110. paasta_tools/paastaapi/model/remote_run_token.py +1 -1
  111. paasta_tools/paastaapi/model/resource.py +1 -1
  112. paasta_tools/paastaapi/model/resource_item.py +1 -1
  113. paasta_tools/paastaapi/model/resource_value.py +1 -1
  114. paasta_tools/paastaapi/model/smartstack_backend.py +1 -1
  115. paasta_tools/paastaapi/model/smartstack_location.py +1 -1
  116. paasta_tools/paastaapi/model/smartstack_status.py +1 -1
  117. paasta_tools/paastaapi/model/task_tail_lines.py +1 -1
  118. paasta_tools/paastaapi/model_utils.py +1 -1
  119. paasta_tools/paastaapi/rest.py +1 -1
  120. paasta_tools/remote_git.py +2 -2
  121. paasta_tools/run-paasta-api-in-dev-mode.py +2 -2
  122. paasta_tools/run-paasta-api-playground.py +2 -2
  123. paasta_tools/setup_kubernetes_job.py +43 -1
  124. paasta_tools/setup_prometheus_adapter_config.py +82 -0
  125. paasta_tools/setup_tron_namespace.py +2 -2
  126. paasta_tools/tron_tools.py +4 -1
  127. paasta_tools/utils.py +29 -11
  128. paasta_tools/yaml_tools.py +1 -1
  129. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_orphans.py +1 -1
  130. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_spark_jobs.py +1 -1
  131. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/generate_deployments_for_service.py +2 -0
  132. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/get_running_task_allocation.py +1 -1
  133. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/ide_helper.py +14 -14
  134. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_update_soa_memcpu.py +10 -14
  135. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/setup_kubernetes_job.py +43 -1
  136. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/setup_prometheus_adapter_config.py +82 -0
  137. paasta_tools-1.35.8.dist-info/METADATA +79 -0
  138. {paasta_tools-1.27.0.dist-info → paasta_tools-1.35.8.dist-info}/RECORD +186 -191
  139. {paasta_tools-1.27.0.dist-info → paasta_tools-1.35.8.dist-info}/WHEEL +1 -1
  140. paasta_tools/frameworks/adhoc_scheduler.py +0 -71
  141. paasta_tools/frameworks/native_scheduler.py +0 -652
  142. paasta_tools/frameworks/task_store.py +0 -245
  143. paasta_tools/mesos_maintenance.py +0 -848
  144. paasta_tools/paasta_native_serviceinit.py +0 -21
  145. paasta_tools-1.27.0.dist-info/METADATA +0 -75
  146. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/apply_external_resources.py +0 -0
  147. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/bounce_log_latency_parser.py +0 -0
  148. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_autoscaler_max_instances.py +0 -0
  149. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_cassandracluster_services_replication.py +0 -0
  150. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_flink_services_health.py +0 -0
  151. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_kubernetes_api.py +0 -0
  152. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_kubernetes_services_replication.py +0 -0
  153. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_manual_oapi_changes.sh +0 -0
  154. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/check_oom_events.py +0 -0
  155. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/cleanup_kubernetes_cr.py +0 -0
  156. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/cleanup_kubernetes_crd.py +0 -0
  157. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/cleanup_kubernetes_jobs.py +0 -0
  158. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/create_dynamodb_table.py +0 -0
  159. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/create_paasta_playground.py +0 -0
  160. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/delete_kubernetes_deployments.py +0 -0
  161. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/emit_allocated_cpu_metrics.py +0 -0
  162. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/generate_all_deployments +0 -0
  163. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/generate_authenticating_services.py +0 -0
  164. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/generate_services_file.py +0 -0
  165. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/generate_services_yaml.py +0 -0
  166. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/habitat_fixer.py +0 -0
  167. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/is_pod_healthy_in_proxy.py +0 -0
  168. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/is_pod_healthy_in_smartstack.py +0 -0
  169. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/kill_bad_containers.py +0 -0
  170. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/kubernetes_remove_evicted_pods.py +0 -0
  171. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/mass-deploy-tag.sh +0 -0
  172. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/mock_patch_checker.py +0 -0
  173. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_cleanup_remote_run_resources.py +0 -0
  174. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_cleanup_stale_nodes.py +0 -0
  175. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_deploy_tron_jobs +0 -0
  176. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_execute_docker_command.py +0 -0
  177. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_secrets_sync.py +0 -0
  178. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/paasta_tabcomplete.sh +0 -0
  179. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/render_template.py +0 -0
  180. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/rightsizer_soaconfigs_update.py +0 -0
  181. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/service_shard_remove.py +0 -0
  182. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/service_shard_update.py +0 -0
  183. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/setup_istio_mesh.py +0 -0
  184. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/setup_kubernetes_cr.py +0 -0
  185. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/setup_kubernetes_crd.py +0 -0
  186. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/setup_kubernetes_internal_crd.py +0 -0
  187. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/shared_ip_check.py +0 -0
  188. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/synapse_srv_namespaces_fact.py +0 -0
  189. {paasta_tools-1.27.0.data → paasta_tools-1.35.8.data}/scripts/timeouts_metrics_prom.py +0 -0
  190. {paasta_tools-1.27.0.dist-info → paasta_tools-1.35.8.dist-info}/entry_points.txt +0 -0
  191. {paasta_tools-1.27.0.dist-info → paasta_tools-1.35.8.dist-info/licenses}/LICENSE +0 -0
  192. {paasta_tools-1.27.0.dist-info → paasta_tools-1.35.8.dist-info}/top_level.txt +0 -0
@@ -157,6 +157,8 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
157
157
  help="Name of the service which you wish to mark for deployment. Leading "
158
158
  '"services-" will be stripped.',
159
159
  required=True,
160
+ # strip any potential trailing / for folks tab-completing directories
161
+ type=lambda x: x.rstrip("/"),
160
162
  )
161
163
  arg_service.completer = lazy_choices_completer(list_services) # type: ignore
162
164
  list_parser.add_argument(
@@ -1157,7 +1159,7 @@ class MarkForDeploymentProcess(RollbackSlackDeploymentProcess):
1157
1159
 
1158
1160
  def on_enter_deploy_errored(self) -> None:
1159
1161
  report_waiting_aborted(self.service, self.deploy_group)
1160
- self.update_slack_status(f"Deploy aborted, but it will still try to converge.")
1162
+ self.update_slack_status("Deploy aborted, but it will still try to converge.")
1161
1163
  self.send_manual_rollback_instructions()
1162
1164
  if self.deploy_group_is_set_to_notify("notify_after_abort"):
1163
1165
  self.ping_authors("Deploy errored")
@@ -1264,7 +1266,15 @@ class MarkForDeploymentProcess(RollbackSlackDeploymentProcess):
1264
1266
  self.ping_authors()
1265
1267
 
1266
1268
  def send_manual_rollback_instructions(self) -> None:
1267
- if self.deployment_version != self.old_deployment_version:
1269
+ # NOTE: new deploy groups are not exactly a particularly frequent occurrence, but
1270
+ # we want to prevent sending messages that look like
1271
+ # `If you need to roll back manually, run: paasta rollback --service $S --deploy-group $G --commit None`
1272
+ # since that's not actually valid/actionable.
1273
+ # thus the seemingly out-of-nowhere old_git_sha check: new deploy groups won't have value set there.
1274
+ if (
1275
+ self.deployment_version != self.old_deployment_version
1276
+ and self.old_git_sha is not None
1277
+ ):
1268
1278
  extra_rollback_args = ""
1269
1279
  if self.old_deployment_version.image_version:
1270
1280
  extra_rollback_args = (
@@ -1317,7 +1327,7 @@ class MarkForDeploymentProcess(RollbackSlackDeploymentProcess):
1317
1327
  inactive_button_texts = {
1318
1328
  "forward": f"Continue Forward to {version_short_str} :arrow_forward:",
1319
1329
  "complete": f"Complete deploy to {version_short_str} :white_check_mark:",
1320
- "snooze": f"Reset countdown",
1330
+ "snooze": "Reset countdown",
1321
1331
  "enable_auto_rollbacks": "Enable auto rollbacks :eyes:",
1322
1332
  "disable_auto_rollbacks": "Disable auto rollbacks :close_eyes_monkey:",
1323
1333
  }
@@ -1843,7 +1853,7 @@ async def wait_for_deployment(
1843
1853
  system_paasta_config.get_mark_for_deployment_default_time_before_first_diagnosis()
1844
1854
  )
1845
1855
 
1846
- with progressbar.ProgressBar(maxval=total_instances) as bar:
1856
+ with progressbar.ProgressBar(max_value=total_instances) as bar:
1847
1857
  instance_done_futures = []
1848
1858
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
1849
1859
  for cluster, instance_configs in instance_configs_per_cluster.items():
@@ -44,9 +44,10 @@ def add_subparser(subparsers) -> None:
44
44
  mesh_status_parser.add_argument(
45
45
  "-s",
46
46
  "--service",
47
- type=str,
48
47
  help="The name of the service you wish to inspect",
49
48
  required=True,
49
+ # strip any potential trailing / for folks tab-completing directories
50
+ type=lambda x: x.rstrip("/"),
50
51
  ).completer = lazy_choices_completer(list_services)
51
52
  mesh_status_parser.add_argument(
52
53
  "-i",
@@ -124,7 +125,7 @@ def paasta_mesh_status_on_api_endpoint(
124
125
  [PaastaColors.red(f"Could not connect to API: {exc.__class__.__name__}")],
125
126
  )
126
127
  except Exception as e:
127
- output = [PaastaColors.red(f"Exception when talking to the API:")]
128
+ output = [PaastaColors.red("Exception when talking to the API:")]
128
129
  output.extend(str(e).split("\n"))
129
130
  return 1, output
130
131
 
@@ -62,6 +62,8 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
62
62
  help='Name of service for which you wish to upload a docker image. Leading "services-", '
63
63
  "as included in a Jenkins job name, will be stripped.",
64
64
  required=True,
65
+ # strip any potential trailing / for folks tab-completing directories
66
+ type=lambda x: x.rstrip("/"),
65
67
  )
66
68
  list_parser.add_argument(
67
69
  "-c",
@@ -14,6 +14,7 @@
14
14
  # limitations under the License.
15
15
  import argparse
16
16
  import json
17
+ import os
17
18
  import shutil
18
19
  import subprocess
19
20
  import sys
@@ -24,6 +25,8 @@ from paasta_tools.cli.utils import get_paasta_oapi_api_clustername
24
25
  from paasta_tools.cli.utils import get_paasta_oapi_client_with_auth
25
26
  from paasta_tools.cli.utils import lazy_choices_completer
26
27
  from paasta_tools.cli.utils import run_interactive_cli
28
+ from paasta_tools.kubernetes.remote_run import format_remote_run_job_name
29
+ from paasta_tools.kubernetes.remote_run import load_eks_or_adhoc_deployment_config
27
30
  from paasta_tools.kubernetes.remote_run import TOOLBOX_MOCK_SERVICE
28
31
  from paasta_tools.paastaapi.exceptions import ApiException
29
32
  from paasta_tools.paastaapi.model.remote_run_start import RemoteRunStart
@@ -39,8 +42,14 @@ from paasta_tools.utils import SystemPaastaConfig
39
42
  KUBECTL_EXEC_CMD_TEMPLATE = (
40
43
  "{kubectl_wrapper} --token {token} exec -it -n {namespace} {pod} -- /bin/bash"
41
44
  )
42
- KUBECTL_CP_CMD_TEMPLATE = (
43
- "{kubectl_wrapper} --token {token} -n {namespace} cp {filename} {pod}:/tmp/"
45
+ KUBECTL_CP_TO_CMD_TEMPLATE = (
46
+ "{kubectl_wrapper} --token {token} -n {namespace} cp {source} {pod}:{dest}"
47
+ )
48
+ KUBECTL_CP_FROM_CMD_TEMPLATE = (
49
+ "{kubectl_wrapper} --token {token} -n {namespace} cp {pod}:{source} {dest}"
50
+ )
51
+ KUBECTL_LOGS_CMD_TEMPLATE = (
52
+ "{kubectl_wrapper} --token {token} logs -n {namespace} -f {pod}"
44
53
  )
45
54
 
46
55
 
@@ -59,6 +68,13 @@ def _list_services_and_toolboxes() -> List[str]:
59
68
  )
60
69
 
61
70
 
71
+ def _get_kubectl_wrapper(cluster: str) -> str:
72
+ kubectl_wrapper = f"kubectl-eks-{cluster}"
73
+ if not shutil.which(kubectl_wrapper):
74
+ kubectl_wrapper = f"kubectl-{cluster}"
75
+ return kubectl_wrapper
76
+
77
+
62
78
  def parse_error(body: str) -> str:
63
79
  try:
64
80
  body_object = json.loads(body)
@@ -71,6 +87,81 @@ def parse_error(body: str) -> str:
71
87
  )
72
88
 
73
89
 
90
+ def paasta_remote_run_copy(
91
+ args: argparse.Namespace,
92
+ system_paasta_config: SystemPaastaConfig,
93
+ ) -> int:
94
+ client = get_paasta_oapi_client_with_auth(
95
+ cluster=get_paasta_oapi_api_clustername(cluster=args.cluster, is_eks=True),
96
+ system_paasta_config=system_paasta_config,
97
+ )
98
+ if not client:
99
+ print("Cannot get a paasta-api client")
100
+ return 1
101
+
102
+ # Create the config and extract the job name
103
+ user = get_username()
104
+ deployment_config = load_eks_or_adhoc_deployment_config(
105
+ args.service, args.instance, args.cluster, args.toolbox, user
106
+ )
107
+ deployment_name = deployment_config.get_sanitised_deployment_name()
108
+ job_name = format_remote_run_job_name(deployment_name, user)
109
+
110
+ poll_response = client.remote_run.remote_run_poll(
111
+ service=args.service,
112
+ instance=args.instance,
113
+ job_name=job_name,
114
+ user=user,
115
+ toolbox=args.toolbox,
116
+ )
117
+ if poll_response.status != 200:
118
+ print(
119
+ "Unable to find running remote-run pod: have you started one with `paasta remote-run start`?"
120
+ )
121
+ return 1
122
+
123
+ if args.to_pod:
124
+ # Create the toolbox copy command
125
+ exec_command = f"scp -A {args.copy_file_source} {poll_response.pod_address}:{args.copy_file_dest}"
126
+ # Pick the correct template and force /tmp/ for non-toolbox
127
+ kubectl_cp_template = KUBECTL_CP_TO_CMD_TEMPLATE
128
+ if not args.copy_file_dest.startswith("/tmp") and not args.toolbox:
129
+ args.copy_file_dest = os.path.join("/tmp/", args.copy_file_dest)
130
+ else:
131
+ exec_command = f"scp -A {poll_response.pod_address}:{args.copy_file_source} {args.copy_file_dest}"
132
+ kubectl_cp_template = KUBECTL_CP_FROM_CMD_TEMPLATE
133
+
134
+ # Kubectl cp doesnt like directories as a destination
135
+ if os.path.isdir(args.copy_file_dest):
136
+ args.copy_file_dest = os.path.join(
137
+ args.copy_file_dest, os.path.basename(args.copy_file_source)
138
+ )
139
+
140
+ if args.toolbox:
141
+ cp_command = exec_command.split(" ")
142
+ else:
143
+ token_response = client.remote_run.remote_run_token(
144
+ args.service, args.instance, user
145
+ )
146
+ kubectl_wrapper = _get_kubectl_wrapper(args.cluster)
147
+ cp_command = kubectl_cp_template.format(
148
+ kubectl_wrapper=kubectl_wrapper,
149
+ namespace=poll_response.namespace,
150
+ pod=poll_response.pod_name,
151
+ source=args.copy_file_source,
152
+ dest=args.copy_file_dest,
153
+ token=token_response.token,
154
+ ).split(" ")
155
+ call = subprocess.run(cp_command, capture_output=True)
156
+ if call.returncode != 0:
157
+ print("Error copying file from remote-run pod: ", file=sys.stderr)
158
+ print(call.stderr.decode("utf-8"), file=sys.stderr)
159
+ return 1
160
+ print(f"{args.copy_file_source} successfully copied to {args.copy_file_dest}")
161
+
162
+ return 0
163
+
164
+
74
165
  def paasta_remote_run_start(
75
166
  args: argparse.Namespace,
76
167
  system_paasta_config: SystemPaastaConfig,
@@ -84,7 +175,6 @@ def paasta_remote_run_start(
84
175
  if not client:
85
176
  print("Cannot get a paasta-api client")
86
177
  return 1
87
-
88
178
  user = get_username()
89
179
  try:
90
180
  start_response = client.remote_run.remote_run_start(
@@ -96,6 +186,7 @@ def paasta_remote_run_start(
96
186
  recreate=args.recreate,
97
187
  max_duration=args.max_duration,
98
188
  toolbox=args.toolbox,
189
+ command=args.cmd or "",
99
190
  ),
100
191
  )
101
192
  except ApiException as e:
@@ -132,7 +223,7 @@ def paasta_remote_run_start(
132
223
  print(f"{status_prefix}Timed out while waiting for job to start")
133
224
  return 1
134
225
 
135
- if not args.interactive and not args.toolbox:
226
+ if not (args.interactive or args.toolbox or args.follow):
136
227
  print("Successfully started remote-run job")
137
228
  return 0
138
229
 
@@ -148,10 +239,10 @@ def paasta_remote_run_start(
148
239
  token_response = client.remote_run.remote_run_token(
149
240
  args.service, args.instance, user
150
241
  )
151
- kubectl_wrapper = f"kubectl-eks-{args.cluster}"
152
- if not shutil.which(kubectl_wrapper):
153
- kubectl_wrapper = f"kubectl-{args.cluster}"
154
- exec_command = KUBECTL_EXEC_CMD_TEMPLATE.format(
242
+ kubectl_wrapper = _get_kubectl_wrapper(args.cluster)
243
+ exec_command = (
244
+ KUBECTL_LOGS_CMD_TEMPLATE if args.follow else KUBECTL_EXEC_CMD_TEMPLATE
245
+ ).format(
155
246
  kubectl_wrapper=kubectl_wrapper,
156
247
  namespace=poll_response.namespace,
157
248
  pod=poll_response.pod_name,
@@ -160,11 +251,12 @@ def paasta_remote_run_start(
160
251
 
161
252
  if args.copy_file:
162
253
  for filename in args.copy_file:
163
- cp_command = KUBECTL_CP_CMD_TEMPLATE.format(
254
+ cp_command = KUBECTL_CP_TO_CMD_TEMPLATE.format(
164
255
  kubectl_wrapper=kubectl_wrapper,
165
256
  namespace=poll_response.namespace,
166
257
  pod=poll_response.pod_name,
167
- filename=filename,
258
+ source=filename,
259
+ dest=os.path.join("/tmp", os.path.basename(filename)),
168
260
  token=token_response.token,
169
261
  ).split(" ")
170
262
  call = subprocess.run(cp_command, capture_output=True)
@@ -203,11 +295,20 @@ def paasta_remote_run_stop(
203
295
 
204
296
 
205
297
  def add_common_args_to_parser(parser: argparse.ArgumentParser):
298
+ def _validate_service_name(name: str) -> str:
299
+ # remove trailing slash if present for folks tab-completing directories
300
+ if name.rstrip("/") not in _list_services_and_toolboxes():
301
+ raise ValueError(f"{name} is not a known service name")
302
+ return name
303
+
206
304
  service_arg = parser.add_argument(
207
305
  "-s",
208
306
  "--service",
209
307
  help="The name of the service you wish to inspect. Required.",
210
308
  required=True,
309
+ # not using `choices` for validation to avoid the help text
310
+ # for the command being incredibly large
311
+ type=_validate_service_name,
211
312
  )
212
313
  service_arg.completer = lazy_choices_completer(_list_services_and_toolboxes) # type: ignore
213
314
  instance_or_toolbox = parser.add_mutually_exclusive_group()
@@ -231,6 +332,7 @@ def add_common_args_to_parser(parser: argparse.ArgumentParser):
231
332
  "--cluster",
232
333
  help="The name of the cluster you wish to run your task on. Required.",
233
334
  required=True,
335
+ choices=list_clusters(),
234
336
  )
235
337
  cluster_arg.completer = lazy_choices_completer(list_clusters) # type: ignore
236
338
 
@@ -249,7 +351,8 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
249
351
  description="Starts or connects to a remote-run-job",
250
352
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
251
353
  )
252
- start_parser.add_argument(
354
+ cmd_or_interactive = start_parser.add_mutually_exclusive_group()
355
+ cmd_or_interactive.add_argument(
253
356
  "-I",
254
357
  "--interactive",
255
358
  help=(
@@ -259,6 +362,15 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
259
362
  action="store_true",
260
363
  default=False,
261
364
  )
365
+ cmd_or_interactive.add_argument(
366
+ "-C",
367
+ "--cmd",
368
+ help=(
369
+ "Run container with particular command, rather than the one specified in soa-configs"
370
+ ),
371
+ required=False,
372
+ default=None,
373
+ )
262
374
  start_parser.add_argument(
263
375
  "-m",
264
376
  "--max-duration",
@@ -267,7 +379,14 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
267
379
  "automatically stopped (capped by the API backend)"
268
380
  ),
269
381
  type=int,
270
- default=1800,
382
+ default=7200,
383
+ )
384
+ start_parser.add_argument(
385
+ "-f",
386
+ "--follow",
387
+ help="Attach to and follow container output",
388
+ action="store_true",
389
+ default=False,
271
390
  )
272
391
  start_parser.add_argument(
273
392
  "-r",
@@ -295,8 +414,31 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
295
414
  description="Stop your remote-run job if it exists",
296
415
  formatter_class=argparse.ArgumentDefaultsHelpFormatter,
297
416
  )
417
+ copy_parser = subparsers.add_parser(
418
+ "copy",
419
+ help="Copy a file from a running remote-run job",
420
+ description="Copy a file from a running remote-run job",
421
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
422
+ )
423
+ copy_parser.add_argument(
424
+ "copy_file_source",
425
+ help="Location of the file within the pod",
426
+ type=str,
427
+ )
428
+ copy_parser.add_argument(
429
+ "copy_file_dest",
430
+ help="Location on the local host to copy the file to",
431
+ type=str,
432
+ )
433
+ copy_parser.add_argument(
434
+ "--to-pod",
435
+ help="Copy a file to the pod instead of from the pod",
436
+ action="store_true",
437
+ default=False,
438
+ )
298
439
  add_common_args_to_parser(start_parser)
299
440
  add_common_args_to_parser(stop_parser)
441
+ add_common_args_to_parser(copy_parser)
300
442
  remote_run_parser.set_defaults(command=paasta_remote_run)
301
443
 
302
444
 
@@ -306,4 +448,6 @@ def paasta_remote_run(args: argparse.Namespace) -> int:
306
448
  return paasta_remote_run_start(args, system_paasta_config)
307
449
  elif args.remote_run_command == "stop":
308
450
  return paasta_remote_run_stop(args, system_paasta_config)
451
+ elif args.remote_run_command == "copy":
452
+ return paasta_remote_run_copy(args, system_paasta_config)
309
453
  raise ValueError(f"Unsupported subcommand: {args.remote_run_command}")
@@ -107,7 +107,11 @@ def add_subparser(subparsers: argparse._SubParsersAction) -> None:
107
107
  )
108
108
 
109
109
  arg_service = list_parser.add_argument(
110
- "-s", "--service", help='Name of the service to rollback (e.g. "service1")'
110
+ "-s",
111
+ "--service",
112
+ help='Name of the service to rollback (e.g. "service1")',
113
+ # strip any potential trailing / for folks tab-completing directories
114
+ type=lambda x: x.rstrip("/"),
111
115
  )
112
116
  arg_service.completer = lazy_choices_completer(list_services) # type: ignore
113
117
  list_parser.add_argument(
@@ -340,7 +344,7 @@ def paasta_rollback(args: argparse.Namespace) -> int:
340
344
  )
341
345
  print(
342
346
  PaastaColors.yellow(
343
- f"WARNING: Failing to do so means that Jenkins will redeploy the latest code on the next scheduled build!"
347
+ "WARNING: Failing to do so means that Jenkins will redeploy the latest code on the next scheduled build!"
344
348
  )
345
349
  )
346
350
 
@@ -212,7 +212,7 @@ def _add_vault_auth_args(parser: argparse.ArgumentParser):
212
212
  dest="vault_auth_method",
213
213
  required=False,
214
214
  default="token",
215
- choices=["token", "ldap"],
215
+ choices=["token", "okta"],
216
216
  )
217
217
  parser.add_argument(
218
218
  "--vault-token-file",
@@ -246,6 +246,8 @@ def _add_common_args(parser: argparse.ArgumentParser, allow_shared: bool = True)
246
246
  "--service",
247
247
  required=not allow_shared,
248
248
  help="The name of the service on which you wish to act",
249
+ # strip any potential trailing / for folks tab-completing directories
250
+ type=lambda x: x.rstrip("/"),
249
251
  ).completer = lazy_choices_completer(list_services)
250
252
 
251
253
  if allow_shared:
@@ -456,7 +458,7 @@ def paasta_secret(args):
456
458
  "vault_token_file": args.vault_token_file,
457
459
  # best solution so far is to change the below string to "token",
458
460
  # so that token file is picked up from argparse
459
- "vault_auth_method": "ldap", # must have LDAP to get 2FA push for prod
461
+ "vault_auth_method": "okta", # must use Okta to get 2FA push
460
462
  },
461
463
  )
462
464
  secret_provider.write_secret(
@@ -28,6 +28,8 @@ def add_subparser(subparsers):
28
28
  help='Name of service for which you wish to check. Leading "services-", as included in a '
29
29
  "Jenkins job name, will be stripped.",
30
30
  required=True,
31
+ # strip any potential trailing / for folks tab-completing directories
32
+ type=lambda x: x.rstrip("/"),
31
33
  )
32
34
  list_parser.add_argument(
33
35
  "-c", "--commit", help="Git sha of the image to check", required=True
@@ -238,6 +238,8 @@ def add_subparser(subparsers):
238
238
  "--service",
239
239
  help="The name of the service from which the Spark image is built.",
240
240
  default=DEFAULT_SPARK_SERVICE,
241
+ # strip any potential trailing / for folks tab-completing directories
242
+ type=lambda x: x.rstrip("/"),
241
243
  ).completer = lazy_choices_completer(list_services)
242
244
 
243
245
  list_parser.add_argument(
@@ -897,20 +899,22 @@ def configure_and_run_docker_container(
897
899
  )
898
900
  ) # type:ignore
899
901
  environment.update(extra_driver_envs)
902
+ if "jupyter-lab" == args.cmd:
903
+ environment["SPARK_DRIVER_TYPE"] = "jupyter"
900
904
 
901
905
  if args.use_service_auth_token:
902
906
  environment["YELP_SVC_AUTHZ_TOKEN"] = get_service_auth_token()
903
907
 
904
908
  webui_url = get_webui_url(spark_conf["spark.ui.port"])
905
- webui_url_msg = PaastaColors.green(f"\nSpark monitoring URL: ") + f"{webui_url}\n"
909
+ webui_url_msg = PaastaColors.green("\nSpark monitoring URL: ") + f"{webui_url}\n"
906
910
 
907
911
  docker_cmd = get_docker_cmd(args, instance_config, spark_conf_str)
908
912
  if "history-server" in docker_cmd:
909
- print(PaastaColors.green(f"\nSpark history server URL: ") + f"{webui_url}\n")
913
+ print(PaastaColors.green("\nSpark history server URL: ") + f"{webui_url}\n")
910
914
  elif any(c in docker_cmd for c in ["pyspark", "spark-shell", "spark-submit"]):
911
915
  grafana_url = get_grafana_url(spark_conf)
912
916
  dashboard_url_msg = (
913
- PaastaColors.green(f"\nGrafana dashboard: ") + f"{grafana_url}\n"
917
+ PaastaColors.green("\nGrafana dashboard: ") + f"{grafana_url}\n"
914
918
  )
915
919
  print(webui_url_msg)
916
920
  print(dashboard_url_msg)