skypilot-nightly 1.0.0.dev20250520__py3-none-any.whl → 1.0.0.dev20250522__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.
Files changed (85) hide show
  1. sky/__init__.py +2 -2
  2. sky/backends/backend_utils.py +4 -1
  3. sky/backends/cloud_vm_ray_backend.py +56 -37
  4. sky/check.py +3 -3
  5. sky/cli.py +89 -16
  6. sky/client/cli.py +89 -16
  7. sky/client/sdk.py +92 -4
  8. sky/clouds/__init__.py +2 -0
  9. sky/clouds/cloud.py +6 -0
  10. sky/clouds/gcp.py +156 -21
  11. sky/clouds/service_catalog/__init__.py +3 -0
  12. sky/clouds/service_catalog/common.py +9 -2
  13. sky/clouds/service_catalog/constants.py +1 -0
  14. sky/core.py +6 -8
  15. sky/dashboard/out/404.html +1 -1
  16. sky/dashboard/out/_next/static/CzOVV6JpRQBRt5GhZuhyK/_buildManifest.js +1 -0
  17. sky/dashboard/out/_next/static/chunks/236-1a3a9440417720eb.js +6 -0
  18. sky/dashboard/out/_next/static/chunks/37-d584022b0da4ac3b.js +6 -0
  19. sky/dashboard/out/_next/static/chunks/393-e1eaa440481337ec.js +1 -0
  20. sky/dashboard/out/_next/static/chunks/480-f28cd152a98997de.js +1 -0
  21. sky/dashboard/out/_next/static/chunks/{678-206dddca808e6d16.js → 582-683f4f27b81996dc.js} +2 -2
  22. sky/dashboard/out/_next/static/chunks/pages/_app-8cfab319f9fb3ae8.js +1 -0
  23. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-33bc2bec322249b1.js +1 -0
  24. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-e2fc2dd1955e6c36.js +1 -0
  25. sky/dashboard/out/_next/static/chunks/pages/clusters-3a748bd76e5c2984.js +1 -0
  26. sky/dashboard/out/_next/static/chunks/pages/infra-9180cd91cee64b96.js +1 -0
  27. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-70756c2dad850a7e.js +1 -0
  28. sky/dashboard/out/_next/static/chunks/pages/jobs-ecd804b9272f4a7c.js +1 -0
  29. sky/dashboard/out/_next/static/css/7e7ce4ff31d3977b.css +3 -0
  30. sky/dashboard/out/clusters/[cluster]/[job].html +1 -1
  31. sky/dashboard/out/clusters/[cluster].html +1 -1
  32. sky/dashboard/out/clusters.html +1 -1
  33. sky/dashboard/out/index.html +1 -1
  34. sky/dashboard/out/infra.html +1 -0
  35. sky/dashboard/out/jobs/[job].html +1 -1
  36. sky/dashboard/out/jobs.html +1 -1
  37. sky/data/storage.py +1 -0
  38. sky/execution.py +57 -8
  39. sky/jobs/server/core.py +5 -3
  40. sky/jobs/utils.py +38 -7
  41. sky/optimizer.py +41 -39
  42. sky/provision/gcp/constants.py +147 -4
  43. sky/provision/gcp/instance_utils.py +10 -0
  44. sky/provision/gcp/volume_utils.py +247 -0
  45. sky/provision/provisioner.py +16 -7
  46. sky/resources.py +233 -18
  47. sky/serve/serve_utils.py +5 -13
  48. sky/serve/server/core.py +2 -4
  49. sky/server/common.py +60 -14
  50. sky/server/constants.py +2 -0
  51. sky/server/html/token_page.html +154 -0
  52. sky/server/requests/executor.py +3 -6
  53. sky/server/requests/payloads.py +3 -3
  54. sky/server/server.py +40 -8
  55. sky/skypilot_config.py +117 -31
  56. sky/task.py +24 -1
  57. sky/templates/gcp-ray.yml.j2 +44 -1
  58. sky/templates/nebius-ray.yml.j2 +0 -2
  59. sky/utils/admin_policy_utils.py +26 -22
  60. sky/utils/cli_utils/status_utils.py +95 -56
  61. sky/utils/common_utils.py +35 -2
  62. sky/utils/context.py +36 -6
  63. sky/utils/context_utils.py +15 -0
  64. sky/utils/infra_utils.py +175 -0
  65. sky/utils/resources_utils.py +55 -21
  66. sky/utils/schemas.py +111 -5
  67. {skypilot_nightly-1.0.0.dev20250520.dist-info → skypilot_nightly-1.0.0.dev20250522.dist-info}/METADATA +1 -1
  68. {skypilot_nightly-1.0.0.dev20250520.dist-info → skypilot_nightly-1.0.0.dev20250522.dist-info}/RECORD +73 -68
  69. {skypilot_nightly-1.0.0.dev20250520.dist-info → skypilot_nightly-1.0.0.dev20250522.dist-info}/WHEEL +1 -1
  70. sky/dashboard/out/_next/static/8hlc2dkbIDDBOkxtEW7X6/_buildManifest.js +0 -1
  71. sky/dashboard/out/_next/static/chunks/236-f49500b82ad5392d.js +0 -6
  72. sky/dashboard/out/_next/static/chunks/37-0a572fe0dbb89c4d.js +0 -6
  73. sky/dashboard/out/_next/static/chunks/845-0ca6f2c1ba667c3b.js +0 -1
  74. sky/dashboard/out/_next/static/chunks/979-7bf73a4c7cea0f5c.js +0 -1
  75. sky/dashboard/out/_next/static/chunks/pages/_app-e6b013bc3f77ad60.js +0 -1
  76. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]/[job]-e15db85d0ea1fbe1.js +0 -1
  77. sky/dashboard/out/_next/static/chunks/pages/clusters/[cluster]-f383db7389368ea7.js +0 -1
  78. sky/dashboard/out/_next/static/chunks/pages/clusters-a93b93e10b8b074e.js +0 -1
  79. sky/dashboard/out/_next/static/chunks/pages/jobs/[job]-03f279c6741fb48b.js +0 -1
  80. sky/dashboard/out/_next/static/chunks/pages/jobs-a75029b67aab6a2e.js +0 -1
  81. sky/dashboard/out/_next/static/css/c6933bbb2ce7f4dd.css +0 -3
  82. /sky/dashboard/out/_next/static/{8hlc2dkbIDDBOkxtEW7X6 → CzOVV6JpRQBRt5GhZuhyK}/_ssgManifest.js +0 -0
  83. {skypilot_nightly-1.0.0.dev20250520.dist-info → skypilot_nightly-1.0.0.dev20250522.dist-info}/entry_points.txt +0 -0
  84. {skypilot_nightly-1.0.0.dev20250520.dist-info → skypilot_nightly-1.0.0.dev20250522.dist-info}/licenses/LICENSE +0 -0
  85. {skypilot_nightly-1.0.0.dev20250520.dist-info → skypilot_nightly-1.0.0.dev20250522.dist-info}/top_level.txt +0 -0
sky/client/cli.py CHANGED
@@ -78,6 +78,7 @@ from sky.utils import common_utils
78
78
  from sky.utils import controller_utils
79
79
  from sky.utils import dag_utils
80
80
  from sky.utils import env_options
81
+ from sky.utils import infra_utils
81
82
  from sky.utils import log_utils
82
83
  from sky.utils import registry
83
84
  from sky.utils import resources_utils
@@ -345,24 +346,39 @@ _TASK_OPTIONS = [
345
346
  'where the task will be invoked. '
346
347
  'Overrides the "workdir" config in the YAML if both are supplied.'
347
348
  )),
349
+ click.option(
350
+ '--infra',
351
+ required=False,
352
+ type=str,
353
+ help='Infrastructure to use. '
354
+ 'Format: cloud, cloud/region, cloud/region/zone, '
355
+ 'or kubernetes/context-name. '
356
+ 'Examples: aws, aws/us-east-1, aws/us-east-1/us-east-1a, '
357
+ # TODO(zhwu): we have to use `\*` to make sure the docs build
358
+ # not complaining about the `*`, but this will cause `--help`
359
+ # to show `\*` instead of `*`.
360
+ 'aws/\\*/us-east-1a, kubernetes/my-cluster-context.'),
348
361
  click.option(
349
362
  '--cloud',
350
363
  required=False,
351
364
  type=str,
352
365
  help=('The cloud to use. If specified, overrides the "resources.cloud" '
353
- 'config. Passing "none" resets the config.')),
366
+ 'config. Passing "none" resets the config.'),
367
+ hidden=True),
354
368
  click.option(
355
369
  '--region',
356
370
  required=False,
357
371
  type=str,
358
372
  help=('The region to use. If specified, overrides the '
359
- '"resources.region" config. Passing "none" resets the config.')),
373
+ '"resources.region" config. Passing "none" resets the config.'),
374
+ hidden=True),
360
375
  click.option(
361
376
  '--zone',
362
377
  required=False,
363
378
  type=str,
364
379
  help=('The zone to use. If specified, overrides the '
365
- '"resources.zone" config. Passing "none" resets the config.')),
380
+ '"resources.zone" config. Passing "none" resets the config.'),
381
+ hidden=True),
366
382
  click.option(
367
383
  '--num-nodes',
368
384
  required=False,
@@ -1063,6 +1079,33 @@ def cli():
1063
1079
  pass
1064
1080
 
1065
1081
 
1082
+ def _handle_infra_cloud_region_zone_options(infra: Optional[str],
1083
+ cloud: Optional[str],
1084
+ region: Optional[str],
1085
+ zone: Optional[str]):
1086
+ """Handle the backward compatibility for --infra and --cloud/region/zone.
1087
+
1088
+ Returns:
1089
+ cloud, region, zone
1090
+ """
1091
+ if cloud is not None or region is not None or zone is not None:
1092
+ click.secho(
1093
+ 'The --cloud, --region, and --zone options are deprecated. '
1094
+ 'Use --infra instead.',
1095
+ fg='yellow')
1096
+ if infra is not None:
1097
+ with ux_utils.print_exception_no_traceback():
1098
+ raise ValueError('Cannot specify both --infra and '
1099
+ '--cloud, --region, or --zone.')
1100
+
1101
+ if infra is not None:
1102
+ infra_info = infra_utils.InfraInfo.from_str(infra)
1103
+ cloud = infra_info.cloud
1104
+ region = infra_info.region
1105
+ zone = infra_info.zone
1106
+ return cloud, region, zone
1107
+
1108
+
1066
1109
  @cli.command(cls=_DocumentedCodeCommand)
1067
1110
  @config_option(expose_value=True)
1068
1111
  @click.argument('entrypoint',
@@ -1172,6 +1215,7 @@ def launch(
1172
1215
  backend_name: Optional[str],
1173
1216
  name: Optional[str],
1174
1217
  workdir: Optional[str],
1218
+ infra: Optional[str],
1175
1219
  cloud: Optional[str],
1176
1220
  region: Optional[str],
1177
1221
  zone: Optional[str],
@@ -1219,6 +1263,9 @@ def launch(
1219
1263
  if backend_name is None:
1220
1264
  backend_name = backends.CloudVmRayBackend.NAME
1221
1265
 
1266
+ cloud, region, zone = _handle_infra_cloud_region_zone_options(
1267
+ infra, cloud, region, zone)
1268
+
1222
1269
  task_or_dag = _make_task_or_dag_from_entrypoint_with_overrides(
1223
1270
  entrypoint=entrypoint,
1224
1271
  name=name,
@@ -1336,6 +1383,7 @@ def exec(cluster: Optional[str],
1336
1383
  entrypoint: Tuple[str, ...],
1337
1384
  detach_run: bool,
1338
1385
  name: Optional[str],
1386
+ infra: Optional[str],
1339
1387
  cloud: Optional[str],
1340
1388
  region: Optional[str],
1341
1389
  zone: Optional[str],
@@ -1427,6 +1475,9 @@ def exec(cluster: Optional[str],
1427
1475
  controller_utils.check_cluster_name_not_controller(
1428
1476
  cluster, operation_str='Executing task on it')
1429
1477
 
1478
+ cloud, region, zone = _handle_infra_cloud_region_zone_options(
1479
+ infra, cloud, region, zone)
1480
+
1430
1481
  task_or_dag = _make_task_or_dag_from_entrypoint_with_overrides(
1431
1482
  entrypoint=entrypoint,
1432
1483
  name=name,
@@ -3265,7 +3316,7 @@ def _down_or_stop_clusters(
3265
3316
 
3266
3317
  @cli.command(cls=_DocumentedCodeCommand)
3267
3318
  @config_option(expose_value=False)
3268
- @click.argument('clouds', required=False, type=str, nargs=-1)
3319
+ @click.argument('infra_list', required=False, type=str, nargs=-1)
3269
3320
  @click.option('--verbose',
3270
3321
  '-v',
3271
3322
  is_flag=True,
@@ -3273,7 +3324,7 @@ def _down_or_stop_clusters(
3273
3324
  help='Show the activated account for each cloud.')
3274
3325
  @usage_lib.entrypoint
3275
3326
  # pylint: disable=redefined-outer-name
3276
- def check(clouds: Tuple[str], verbose: bool):
3327
+ def check(infra_list: Tuple[str], verbose: bool):
3277
3328
  """Check which clouds are available to use.
3278
3329
 
3279
3330
  This checks access credentials for all clouds supported by SkyPilot. If a
@@ -3295,8 +3346,8 @@ def check(clouds: Tuple[str], verbose: bool):
3295
3346
  # Check only specific clouds - AWS and GCP.
3296
3347
  sky check aws gcp
3297
3348
  """
3298
- clouds_arg = clouds if len(clouds) > 0 else None
3299
- request_id = sdk.check(clouds=clouds_arg, verbose=verbose)
3349
+ infra_arg = infra_list if len(infra_list) > 0 else None
3350
+ request_id = sdk.check(infra_list=infra_arg, verbose=verbose)
3300
3351
  sdk.stream_and_get(request_id)
3301
3352
  api_server_url = server_common.get_server_url()
3302
3353
  click.echo()
@@ -3312,10 +3363,15 @@ def check(clouds: Tuple[str], verbose: bool):
3312
3363
  is_flag=True,
3313
3364
  default=False,
3314
3365
  help='Show details of all GPU/TPU/accelerator offerings.')
3366
+ @click.option('--infra',
3367
+ default=None,
3368
+ type=str,
3369
+ help='Infrastructure to query. Examples: "aws", "aws/us-east-1"')
3315
3370
  @click.option('--cloud',
3316
3371
  default=None,
3317
3372
  type=str,
3318
- help='Cloud provider to query.')
3373
+ help='Cloud provider to query.',
3374
+ hidden=True)
3319
3375
  @click.option(
3320
3376
  '--region',
3321
3377
  required=False,
@@ -3323,6 +3379,7 @@ def check(clouds: Tuple[str], verbose: bool):
3323
3379
  help=
3324
3380
  ('The region to use. If not specified, shows accelerators from all regions.'
3325
3381
  ),
3382
+ hidden=True,
3326
3383
  )
3327
3384
  @click.option(
3328
3385
  '--all-regions',
@@ -3335,6 +3392,7 @@ def check(clouds: Tuple[str], verbose: bool):
3335
3392
  def show_gpus(
3336
3393
  accelerator_str: Optional[str],
3337
3394
  all: bool, # pylint: disable=redefined-builtin
3395
+ infra: Optional[str],
3338
3396
  cloud: Optional[str],
3339
3397
  region: Optional[str],
3340
3398
  all_regions: Optional[bool]):
@@ -3376,6 +3434,11 @@ def show_gpus(
3376
3434
  * ``UTILIZATION`` (Kubernetes only): Total number of GPUs free / available
3377
3435
  in the Kubernetes cluster.
3378
3436
  """
3437
+ cloud, region, _ = _handle_infra_cloud_region_zone_options(infra,
3438
+ cloud,
3439
+ region,
3440
+ zone=None)
3441
+
3379
3442
  # validation for the --region flag
3380
3443
  if region is not None and cloud is None:
3381
3444
  raise click.UsageError(
@@ -3991,6 +4054,7 @@ def jobs_launch(
3991
4054
  name: Optional[str],
3992
4055
  cluster: Optional[str],
3993
4056
  workdir: Optional[str],
4057
+ infra: Optional[str],
3994
4058
  cloud: Optional[str],
3995
4059
  region: Optional[str],
3996
4060
  zone: Optional[str],
@@ -4032,6 +4096,8 @@ def jobs_launch(
4032
4096
  'Use one of the flags as they are alias.')
4033
4097
  name = cluster
4034
4098
  env = _merge_env_vars(env_file, env)
4099
+ cloud, region, zone = _handle_infra_cloud_region_zone_options(
4100
+ infra, cloud, region, zone)
4035
4101
  task_or_dag = _make_task_or_dag_from_entrypoint_with_overrides(
4036
4102
  entrypoint,
4037
4103
  name=name,
@@ -4509,6 +4575,7 @@ def serve_up(
4509
4575
  service_yaml: Tuple[str, ...],
4510
4576
  service_name: Optional[str],
4511
4577
  workdir: Optional[str],
4578
+ infra: Optional[str],
4512
4579
  cloud: Optional[str],
4513
4580
  region: Optional[str],
4514
4581
  zone: Optional[str],
@@ -4555,6 +4622,8 @@ def serve_up(
4555
4622
 
4556
4623
  sky serve up service.yaml
4557
4624
  """
4625
+ cloud, region, zone = _handle_infra_cloud_region_zone_options(
4626
+ infra, cloud, region, zone)
4558
4627
  if service_name is None:
4559
4628
  service_name = serve_lib.generate_service_name()
4560
4629
 
@@ -4621,13 +4690,13 @@ def serve_up(
4621
4690
  @timeline.event
4622
4691
  @usage_lib.entrypoint
4623
4692
  def serve_update(service_name: str, service_yaml: Tuple[str, ...],
4624
- workdir: Optional[str], cloud: Optional[str],
4625
- region: Optional[str], zone: Optional[str],
4626
- num_nodes: Optional[int], use_spot: Optional[bool],
4627
- image_id: Optional[str], env_file: Optional[Dict[str, str]],
4628
- env: List[Tuple[str, str]], gpus: Optional[str],
4629
- instance_type: Optional[str], ports: Tuple[str],
4630
- cpus: Optional[str], memory: Optional[str],
4693
+ workdir: Optional[str], infra: Optional[str],
4694
+ cloud: Optional[str], region: Optional[str],
4695
+ zone: Optional[str], num_nodes: Optional[int],
4696
+ use_spot: Optional[bool], image_id: Optional[str],
4697
+ env_file: Optional[Dict[str, str]], env: List[Tuple[str, str]],
4698
+ gpus: Optional[str], instance_type: Optional[str],
4699
+ ports: Tuple[str], cpus: Optional[str], memory: Optional[str],
4631
4700
  disk_size: Optional[int], disk_tier: Optional[str], mode: str,
4632
4701
  yes: bool, async_call: bool):
4633
4702
  """Update a SkyServe service.
@@ -4659,6 +4728,8 @@ def serve_update(service_name: str, service_yaml: Tuple[str, ...],
4659
4728
  sky serve update --mode blue_green sky-service-16aa new_service.yaml
4660
4729
 
4661
4730
  """
4731
+ cloud, region, zone = _handle_infra_cloud_region_zone_options(
4732
+ infra, cloud, region, zone)
4662
4733
  task = _generate_task_with_service(
4663
4734
  service_name=service_name,
4664
4735
  service_yaml_args=service_yaml,
@@ -5173,6 +5244,7 @@ def benchmark_launch(
5173
5244
  benchmark: str,
5174
5245
  name: Optional[str],
5175
5246
  workdir: Optional[str],
5247
+ infra: Optional[str],
5176
5248
  cloud: Optional[str],
5177
5249
  region: Optional[str],
5178
5250
  zone: Optional[str],
@@ -5206,7 +5278,6 @@ def benchmark_launch(
5206
5278
  raise click.BadParameter(f'Benchmark {benchmark} already exists. '
5207
5279
  'To delete the previous benchmark result, '
5208
5280
  f'run `sky bench delete {benchmark}`.')
5209
-
5210
5281
  entrypoint = ' '.join(entrypoint)
5211
5282
  if not entrypoint:
5212
5283
  raise click.BadParameter('Please specify a task yaml to benchmark.')
@@ -5217,6 +5288,8 @@ def benchmark_launch(
5217
5288
  'Sky Benchmark does not support command line tasks. '
5218
5289
  'Please provide a YAML file.')
5219
5290
  assert config is not None, (is_yaml, config)
5291
+ cloud, region, zone = _handle_infra_cloud_region_zone_options(
5292
+ infra, cloud, region, zone)
5220
5293
 
5221
5294
  click.secho('Benchmarking a task from YAML: ', fg='cyan', nl=False)
5222
5295
  click.secho(entrypoint, bold=True)
sky/client/sdk.py CHANGED
@@ -10,14 +10,19 @@ Usage example:
10
10
  statuses = sky.get(request_id)
11
11
 
12
12
  """
13
+ import base64
14
+ import binascii
13
15
  import getpass
16
+ from http import cookiejar
14
17
  import json
15
18
  import logging
16
19
  import os
17
20
  import pathlib
18
21
  import subprocess
22
+ import time
19
23
  import typing
20
24
  from typing import Any, Dict, List, Optional, Tuple, Union
25
+ from urllib import parse as urlparse
21
26
  import webbrowser
22
27
 
23
28
  import click
@@ -42,6 +47,7 @@ from sky.utils import common
42
47
  from sky.utils import common_utils
43
48
  from sky.utils import dag_utils
44
49
  from sky.utils import env_options
50
+ from sky.utils import infra_utils
45
51
  from sky.utils import rich_utils
46
52
  from sky.utils import status_lib
47
53
  from sky.utils import subprocess_utils
@@ -87,12 +93,12 @@ def stream_response(request_id: Optional[str],
87
93
  @usage_lib.entrypoint
88
94
  @server_common.check_server_healthy_or_start
89
95
  @annotations.client_api
90
- def check(clouds: Optional[Tuple[str]],
96
+ def check(infra_list: Optional[Tuple[str, ...]],
91
97
  verbose: bool) -> server_common.RequestId:
92
98
  """Checks the credentials to enable clouds.
93
99
 
94
100
  Args:
95
- clouds: The clouds to check.
101
+ infra: The infra to check.
96
102
  verbose: Whether to show verbose output.
97
103
 
98
104
  Returns:
@@ -101,6 +107,22 @@ def check(clouds: Optional[Tuple[str]],
101
107
  Request Returns:
102
108
  None
103
109
  """
110
+ if infra_list is None:
111
+ clouds = None
112
+ else:
113
+ specified_clouds = []
114
+ for infra_str in infra_list:
115
+ infra = infra_utils.InfraInfo.from_str(infra_str)
116
+ if infra.cloud is None:
117
+ with ux_utils.print_exception_no_traceback():
118
+ raise ValueError(f'Invalid infra to check: {infra_str}')
119
+ if infra.region is not None or infra.zone is not None:
120
+ region_zone = infra_str.partition('/')[-1]
121
+ logger.warning(f'Infra {infra_str} is specified, but `check` '
122
+ f'only supports checking {infra.cloud}, '
123
+ f'ignoring {region_zone}')
124
+ specified_clouds.append(infra.cloud)
125
+ clouds = tuple(specified_clouds)
104
126
  body = payloads.CheckBody(clouds=clouds, verbose=verbose)
105
127
  response = requests.post(f'{server_common.get_server_url()}/check',
106
128
  json=json.loads(body.model_dump_json()),
@@ -344,7 +366,7 @@ def launch(
344
366
  import sky
345
367
  task = sky.Task(run='echo hello SkyPilot')
346
368
  task.set_resources(
347
- sky.Resources(cloud=sky.AWS(), accelerators='V100:4'))
369
+ sky.Resources(infra='aws', accelerators='V100:4'))
348
370
  sky.launch(task, cluster_name='my-cluster')
349
371
 
350
372
 
@@ -1824,7 +1846,73 @@ def api_login(endpoint: Optional[str] = None) -> None:
1824
1846
  not endpoint.startswith('https://')):
1825
1847
  raise click.BadParameter('Endpoint must be a valid URL.')
1826
1848
 
1827
- server_common.check_server_healthy(endpoint)
1849
+ server_status = server_common.check_server_healthy(endpoint)
1850
+ if server_status == server_common.ApiServerStatus.NEEDS_AUTH:
1851
+ # We detected an auth proxy, so go through the auth proxy cookie flow.
1852
+ parsed_url = urlparse.urlparse(endpoint)
1853
+ token_url = f'{endpoint}/token'
1854
+ click.echo('Authentication is needed. Please visit this URL setup up '
1855
+ f'the token:{colorama.Style.BRIGHT}\n\n{token_url}'
1856
+ f'\n{colorama.Style.RESET_ALL}')
1857
+ if webbrowser.open(token_url):
1858
+ click.echo('Opening browser...')
1859
+ token: str = click.prompt('Paste the token')
1860
+
1861
+ # Parse the token.
1862
+ # b64decode will ignore invalid characters, but does some length and
1863
+ # padding checks.
1864
+ try:
1865
+ data = base64.b64decode(token)
1866
+ except binascii.Error as e:
1867
+ raise ValueError(f'Malformed token: {token}') from e
1868
+ logger.debug(f'Token data: {data!r}')
1869
+ try:
1870
+ cookie_dict = json.loads(data)
1871
+ except (json.JSONDecodeError, UnicodeDecodeError) as e:
1872
+ raise ValueError(f'Malformed token data: {data!r}') from e
1873
+ if not isinstance(cookie_dict, dict):
1874
+ raise ValueError(f'Malformed token JSON: {cookie_dict}')
1875
+
1876
+ cookie_jar = cookiejar.MozillaCookieJar()
1877
+ for (name, value) in cookie_dict.items():
1878
+ # dict keys in JSON must be strings
1879
+ assert isinstance(name, str)
1880
+ if not isinstance(value, str):
1881
+ raise ValueError('Malformed token - bad key/value: '
1882
+ f'{name}: {value}')
1883
+
1884
+ # See CookieJar._cookie_from_cookie_tuple
1885
+ # oauth2proxy default is Max-Age 604800
1886
+ expires = int(time.time()) + 604800
1887
+ domain = str(parsed_url.hostname)
1888
+ domain_initial_dot = domain.startswith('.')
1889
+ if not domain_initial_dot:
1890
+ domain = '.' + domain
1891
+
1892
+ cookie_jar.set_cookie(
1893
+ cookiejar.Cookie(
1894
+ version=0,
1895
+ name=name,
1896
+ value=value,
1897
+ port=None,
1898
+ port_specified=False,
1899
+ domain=domain,
1900
+ domain_specified=True,
1901
+ domain_initial_dot=domain_initial_dot,
1902
+ path='',
1903
+ path_specified=False,
1904
+ secure=False,
1905
+ expires=expires,
1906
+ discard=False,
1907
+ comment=None,
1908
+ comment_url=None,
1909
+ rest=dict(),
1910
+ ))
1911
+
1912
+ # Now that the cookies are parsed, save them to the cookie jar.
1913
+ cookie_jar_path = os.path.expanduser(
1914
+ server_common.get_api_cookie_jar_path())
1915
+ cookie_jar.save(cookie_jar_path)
1828
1916
 
1829
1917
  # Set the endpoint in the config file
1830
1918
  config_path = pathlib.Path(
sky/clouds/__init__.py CHANGED
@@ -3,6 +3,7 @@
3
3
  from sky.clouds.cloud import Cloud
4
4
  from sky.clouds.cloud import cloud_in_iterable
5
5
  from sky.clouds.cloud import CloudImplementationFeatures
6
+ from sky.clouds.cloud import DummyCloud
6
7
  from sky.clouds.cloud import OpenPortsVersion
7
8
  from sky.clouds.cloud import ProvisionerVersion
8
9
  from sky.clouds.cloud import Region
@@ -34,6 +35,7 @@ __all__ = [
34
35
  'Azure',
35
36
  'Cloud',
36
37
  'Cudo',
38
+ 'DummyCloud',
37
39
  'GCP',
38
40
  'Lambda',
39
41
  'DO',
sky/clouds/cloud.py CHANGED
@@ -888,6 +888,12 @@ class Cloud:
888
888
  return state
889
889
 
890
890
 
891
+ class DummyCloud(Cloud):
892
+ """A dummy Cloud that has zero egress cost from/to for optimization
893
+ purpose."""
894
+ pass
895
+
896
+
891
897
  # === Helper functions ===
892
898
  def cloud_in_iterable(cloud: Cloud, cloud_list: Iterable[Cloud]) -> bool:
893
899
  """Returns whether the cloud is in the given cloud list."""