Skip to content

Commit db5d15b

Browse files
authored
[GCP/Auth] Use the application key in the env var (#2116)
* [GCP] use the application key in the env var * rename * lint * Add doc and fix the test * fix test * format * longer waiting time for azure * fix * Address comments * format
1 parent f225967 commit db5d15b

File tree

6 files changed

+67
-5
lines changed

6 files changed

+67
-5
lines changed

docs/source/cloud-setup/cloud-auth.rst

+22
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,25 @@ Example of mixing a default profile and an SSO profile:
4545
4646
$ # A cluster launched under a different profile.
4747
$ AWS_PROFILE=AdministratorAccess-12345 sky launch --cloud aws -c my-sso-cluster
48+
49+
50+
GCP
51+
-------------------------------
52+
53+
.. _gcp-service-account:
54+
55+
GCP Service Account
56+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
57+
58+
`GCP Service Account <https://cloud.google.com/iam/docs/service-account-overview>`__ is supported.
59+
60+
To use it to access GCP with SkyPilot, you need to setup the credentials:
61+
62+
1. Download the key for the service account from the `GCP console <https://console.cloud.google.com/iam-admin/serviceaccounts>`__.
63+
2. Set the environment variable `GOOGLE_APPLICATION_CREDENTIALS` to the path of the key file, and configure the gcloud CLI tool:
64+
65+
.. code-block:: console
66+
67+
$ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json
68+
$ gcloud auth activate-service-account --key-file=$GOOGLE_APPLICATION_CREDENTIALS
69+
$ gcloud config set project your-project-id

docs/source/getting-started/installation.rst

+2
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ Note: if you encounter *Authorization Error (Error 400: invalid_request)* with t
9393

9494
If you are using multiple GCP projects, list all the projects by :code:`gcloud project list` and activate one by :code:`gcloud config set project <PROJECT_ID>` (See `GCP docs <https://cloud.google.com/sdk/gcloud/reference/config/set>`_).
9595

96+
To use service account to access GCP for SkyPilot, see :ref:`here<gcp-service-account>` for instructions.
97+
9698
**Optional**: To create a new GCP user with minimal permissions for SkyPilot, see :ref:`GCP User Creation <cloud-permissions-gcp>`.
9799

98100
Azure

sky/clouds/gcp.py

+33-3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@
2222

2323
logger = sky_logging.init_logger(__name__)
2424

25+
# Env var pointing to any service account key. If it exists, this path takes
26+
# priority over the DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH below, and will be
27+
# used instead for SkyPilot-launched instances. This is the same behavior as
28+
# gcloud:
29+
# https://cloud.google.com/docs/authentication/provide-credentials-adc#local-key
30+
_GCP_APPLICATION_CREDENTIAL_ENV = 'GOOGLE_APPLICATION_CREDENTIALS'
2531
DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH: str = os.path.expanduser(
2632
'~/.config/gcloud/'
2733
'application_default_credentials.json')
@@ -44,7 +50,6 @@
4450
# Minimum set of files under ~/.config/gcloud that grant GCP access.
4551
_CREDENTIAL_FILES = [
4652
'credentials.db',
47-
'application_default_credentials.json',
4853
'access_tokens.db',
4954
'configurations',
5055
'legacy_credentials',
@@ -470,6 +475,24 @@ def get_vcpus_mem_from_instance_type(
470475
return service_catalog.get_vcpus_mem_from_instance_type(instance_type,
471476
clouds='gcp')
472477

478+
@classmethod
479+
def _find_application_key_path(cls) -> str:
480+
# Check the application default credentials in the environment variable.
481+
# If the file does not exist, fallback to the default path.
482+
application_key_path = os.environ.get(_GCP_APPLICATION_CREDENTIAL_ENV,
483+
None)
484+
if application_key_path is not None:
485+
if not os.path.isfile(os.path.expanduser(application_key_path)):
486+
raise FileNotFoundError(
487+
f'{_GCP_APPLICATION_CREDENTIAL_ENV}={application_key_path},'
488+
' but the file does not exist.')
489+
return application_key_path
490+
if (not os.path.isfile(
491+
os.path.expanduser(DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH))):
492+
# Fallback to the default application credential path.
493+
raise FileNotFoundError(DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH)
494+
return DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH
495+
473496
@classmethod
474497
def check_credentials(cls) -> Tuple[bool, Optional[str]]:
475498
"""Checks if the user has access credentials to this cloud."""
@@ -485,10 +508,12 @@ def check_credentials(cls) -> Tuple[bool, Optional[str]]:
485508
for file in [
486509
'~/.config/gcloud/access_tokens.db',
487510
'~/.config/gcloud/credentials.db',
488-
'~/.config/gcloud/application_default_credentials.json'
489511
]:
490512
if not os.path.isfile(os.path.expanduser(file)):
491513
raise FileNotFoundError(file)
514+
515+
cls._find_application_key_path()
516+
492517
# Check the installation of google-cloud-sdk.
493518
_run_output('gcloud --version')
494519

@@ -500,7 +525,7 @@ def check_credentials(cls) -> Tuple[bool, Optional[str]]:
500525
except (auth.exceptions.DefaultCredentialsError,
501526
subprocess.CalledProcessError,
502527
exceptions.CloudUserIdentityError, FileNotFoundError,
503-
ImportError):
528+
ImportError) as e:
504529
# See also: https://stackoverflow.com/a/53307505/1165051
505530
return False, (
506531
'GCP tools are not installed or credentials are not set. '
@@ -515,6 +540,7 @@ def check_credentials(cls) -> Tuple[bool, Optional[str]]:
515540
' $ gcloud auth application-default login\n '
516541
'For more info: '
517542
'https://skypilot.readthedocs.io/en/latest/getting-started/installation.html' # pylint: disable=line-too-long
543+
f'\nDetails: {common_utils.format_exception(e, use_bracket=True)}'
518544
)
519545

520546
# Check APIs.
@@ -611,6 +637,10 @@ def get_credential_file_mounts(self) -> Dict[str, str]:
611637
f'~/.config/gcloud/{filename}': f'~/.config/gcloud/{filename}'
612638
for filename in _CREDENTIAL_FILES
613639
}
640+
# Upload the application key path to the default path, so that
641+
# autostop and GCS can be accessed on the remote cluster.
642+
credentials[DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH] = (
643+
self._find_application_key_path())
614644
credentials[GCP_CONFIG_SKY_BACKUP_PATH] = GCP_CONFIG_SKY_BACKUP_PATH
615645
return credentials
616646

tests/conftest.py

+3
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,9 @@ def enable_all_clouds(monkeypatch):
189189
prefix='tmp_backup_config_default', delete=False)
190190
monkeypatch.setattr('sky.clouds.gcp.GCP_CONFIG_SKY_BACKUP_PATH',
191191
config_file_backup.name)
192+
monkeypatch.setattr(
193+
'sky.clouds.gcp.DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH',
194+
config_file_backup.name)
192195
monkeypatch.setenv('OCI_CONFIG', config_file_backup.name)
193196

194197

tests/test_optimizer_dryruns.py

+5
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ def _test_parse_accelerators(spec, expected_accelerators):
4242
# clouds are enabled, so we monkeypatch the `sky.global_user_state` module
4343
# to return all three clouds. We also monkeypatch `sky.check.check` so that
4444
# when the optimizer tries calling it to update enabled_clouds, it does not
45+
# TODO: Keep the cloud enabling in sync with the fixture enable_all_clouds
46+
# in tests/conftest.py
4547
# raise exceptions.
4648
def _make_resources(
4749
monkeypatch,
@@ -61,6 +63,9 @@ def _make_resources(
6163
prefix='tmp_backup_config_default', delete=False)
6264
monkeypatch.setattr('sky.clouds.gcp.GCP_CONFIG_SKY_BACKUP_PATH',
6365
config_file_backup.name)
66+
monkeypatch.setattr(
67+
'sky.clouds.gcp.DEFAULT_GCP_APPLICATION_CREDENTIAL_PATH',
68+
config_file_backup.name)
6469
monkeypatch.setenv('OCI_CONFIG', config_file_backup.name)
6570

6671
# Should create Resources here, since it uses the enabled clouds.

tests/test_smoke.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1267,8 +1267,8 @@ def test_azure_start_stop():
12671267
f'sky start -y {name} -i 1',
12681268
f'sky exec {name} examples/azure_start_stop.yaml',
12691269
f'sky logs {name} 3 --status', # Ensure the job succeeded.
1270-
'sleep 180',
1271-
f'sky status -r {name} | grep "INIT\|STOPPED"'
1270+
'sleep 200',
1271+
f's=$(sky status -r {name}) | echo $s && echo $s | grep "INIT\|STOPPED"'
12721272
],
12731273
f'sky down -y {name}',
12741274
timeout=30 * 60, # 30 mins

0 commit comments

Comments
 (0)