Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use Google Auth and Cloud Python APIs instead of gcloud CLI #2083

Merged
merged 22 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
0f79dfb
modified the projects and zones function
swastik959 Oct 22, 2023
3051574
[pre-commit.ci] Apply automatic pre-commit fixes
pre-commit-ci[bot] Oct 22, 2023
5d593b1
modified whole code
swastik959 Nov 3, 2023
53ca190
[pre-commit.ci] Apply automatic pre-commit fixes
pre-commit-ci[bot] Nov 3, 2023
2105e6e
Merge branch 'develop' into swastik
marcelovilla Jul 25, 2024
7d319b5
Fix google dependency versions
marcelovilla Jul 26, 2024
1088d94
Remove unused functions and fix calls to the Google Python APIs
marcelovilla Aug 2, 2024
a0694d1
Merge branch 'develop' into swastik
marcelovilla Aug 2, 2024
97677ec
[pre-commit.ci] Apply automatic pre-commit fixes
pre-commit-ci[bot] Aug 2, 2024
6f3d813
Add grpc-google-iam-v1
marcelovilla Aug 5, 2024
d6498f9
Merge branch 'swastik' of https://github.com/swastik959/nebari into s…
marcelovilla Aug 5, 2024
4daa377
Load credentials explictly from file and get project ID from environe…
marcelovilla Aug 5, 2024
ab7f216
Check if credentials are a file or not before reading them
marcelovilla Aug 5, 2024
4ac3911
Remove gcloud step
marcelovilla Aug 5, 2024
8a47dcd
Add google-auth as an explicit dependency
marcelovilla Aug 6, 2024
7be8fff
Use string ending instead of Path.isfile to check whether env var is …
marcelovilla Aug 6, 2024
bdddbe2
Fix dependency version specifier
marcelovilla Aug 6, 2024
6338899
Fix cleanup functions.
marcelovilla Aug 7, 2024
1c2fec3
Add explicit google auth scopes
marcelovilla Aug 9, 2024
d54739b
Merge branch 'develop' into swastik
marcelovilla Sep 4, 2024
88785a1
Merge branch 'develop' into swastik
marcelovilla Sep 5, 2024
f507f2d
Merge branch 'develop' into swastik
Adam-D-Lewis Sep 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ dependencies = [
"rich==13.5.1",
"ruamel.yaml==0.17.32",
"typer==0.9.0",
"google-cloud-resourcemanager==1.10.1",
"google-cloud-compute==4.15.0",
"google-cloud-storage==2.13.0",
"google-cloud-container==2.32.0",
"google-cloud-iam-credentials== 1.4.1",

]

[project.optional-dependencies]
Expand Down
204 changes: 72 additions & 132 deletions src/_nebari/provider/cloud/google_cloud.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
import functools
import json
import os
import subprocess
from typing import Dict, List

from google.cloud import (
compute_v1,
container_v1,
iam_credentials_v1,
resourcemanager,
storage,
)

from _nebari import constants
from _nebari.provider.cloud.commons import filter_by_highest_supported_k8s_version
from nebari import schema
Expand All @@ -22,129 +28,90 @@ def check_credentials():
def projects() -> Dict[str, str]:
"""Return a dict of available projects."""
check_credentials()
output = subprocess.check_output(
["gcloud", "projects", "list", "--format=json(name,projectId)"]
)
data = json.loads(output)
return {_["name"]: _["projectId"] for _ in data}
client = resourcemanager.Client()
projects = client.list_projects()
project_dict = {project.name: project.project_id for project in projects}

return project_dict


@functools.lru_cache()
def regions(project: str) -> Dict[str, str]:
"""Return a dict of available regions."""
check_credentials()
output = subprocess.check_output(
["gcloud", "compute", "regions", "list", "--project", project, "--format=json"]
client = compute_v1.RegionClient()
request = compute_v1.ListRegionsRequest(
project="project_value",
)
data = json.loads(output.decode("utf-8"))
return {_["description"]: _["name"] for _ in data}
regions = client.list(request=request)
region_dict = {region.description: region.name for region in regions}

return region_dict


@functools.lru_cache()
def zones(project: str, region: str) -> Dict[str, str]:
"""Return a dict of available zones."""
check_credentials()
output = subprocess.check_output(
["gcloud", "compute", "zones", "list", "--project", project, "--format=json"]
client = compute_v1.ZonesClient()
request = compute_v1.ListZonesRequest(
project="project_value",
)
data = json.loads(output.decode("utf-8"))
return {_["description"]: _["name"] for _ in data if _["name"].startswith(region)}
zones = client.list(request=request)
zone_dict = {
zone.description: zone.name for zone in zones if zone.name.startswith(region)
}
return zone_dict


@functools.lru_cache()
def kubernetes_versions(region: str) -> List[str]:
"""Return list of available kubernetes supported by cloud provider. Sorted from oldest to latest."""
check_credentials()
output = subprocess.check_output(
[
"gcloud",
"container",
"get-server-config",
"--region",
region,
"--format=json",
]
client = container_v1.ClusterManagerClient()
request = container_v1.GetServerConfigRequest()
response = client.get_server_config(request=request)
supported_kubernetes_versions = sorted(response.valid_master_versions)
filtered_versions = filter_by_highest_supported_k8s_version(
supported_kubernetes_versions
)
data = json.loads(output.decode("utf-8"))
supported_kubernetes_versions = sorted([_ for _ in data["validMasterVersions"]])
return filter_by_highest_supported_k8s_version(supported_kubernetes_versions)
return filtered_versions


@functools.lru_cache()
def instances(project: str) -> Dict[str, str]:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function would be useful to do the same thing as AWS and Digital Ocean do and check instance types are valid before deploying. I'd be okay if you just open an issue to re-add that functionality at some point though. Search the repo for instances( and see how its used in infrastructure's __init__.py file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Adam-D-Lewis great catch. I just created an issue to track that and make sure we add that functionality for Google Cloud.

"""Return a dict of available instances."""
def instances(project: str, zone: str) -> Dict[str, str]:
"""Return a dict of available instances of a particular zone."""
check_credentials()
output = subprocess.check_output(
[
"gcloud",
"compute",
"machine-types",
"list",
"--project",
project,
"--format=json",
]
client = compute_v1.InstancesClient()
request = compute_v1.ListInstancesRequest(
project="project",
zone="zone",
)
data = json.loads(output.decode("utf-8"))
return {_["description"]: _["name"] for _ in data}
instances = client.list(request=request)
instance_dict = {instances.description: instances.name for instance in instances}
return instance_dict


def cluster_exists(cluster_name: str, project_id: str, region: str) -> bool:
def cluster_exists(cluster_name: str, project_id: str, zone: str) -> bool:
"""Check if a GKE cluster exists."""
try:
subprocess.check_output(
[
"gcloud",
"container",
"clusters",
"describe",
cluster_name,
"--project",
project_id,
"--region",
region,
]
)
return True
except subprocess.CalledProcessError:
return False
client = container_v1.ClusterManagerClient()
request = container_v1.GetClusterRequest()
response = client.get_cluster(request=request, project_id=project_id, zone=zone)

return response is not None


def bucket_exists(bucket_name: str, project_id: str) -> bool:
"""Check if a storage bucket exists."""
try:
print(f"Checking if bucket {bucket_name} exists in project {project_id}.")
subprocess.check_output(
[
"gsutil",
"ls",
f"gs://{bucket_name}/",
"-p",
project_id,
]
)
return True
except subprocess.CalledProcessError:
return False
client = storage.Client(project=project_id)
bucket = client.get_bucket(bucket_name)
return bucket is not None


def service_account_exists(service_account_name: str, project_id: str) -> bool:
"""Check if a service account exists."""
try:
subprocess.check_output(
[
"gcloud",
"iam",
"service-accounts",
"describe",
service_account_name,
"--project",
project_id,
]
)
return True
except subprocess.CalledProcessError:
return False
client = iam_credentials_v1.IAMCredentialsClient()
service_acc = client.service_account_path(project_id, service_account_name)
return service_acc is not None


def delete_cluster(cluster_name: str, project_id: str, region: str):
Expand All @@ -157,24 +124,15 @@ def delete_cluster(cluster_name: str, project_id: str, region: str):
)
return

client = container_v1.ClusterManagerClient()
request = client.DeleteClusterRequest()
try:
subprocess.check_call(
[
"gcloud",
"container",
"clusters",
"delete",
cluster_name,
"--project",
project_id,
"--region",
region,
"--quiet",
]
)
print(f"Successfully deleted cluster {cluster_name}.")
except subprocess.CalledProcessError as e:
print(f"Failed to delete cluster {cluster_name}. Error: {e}")
client.delete_cluster(request=request)
except google.api_core.exceptions.GoogleAPICallError as e:
if e.status_code == 200:
print("Cluster deleted successfully!")
else:
print("error deleting cluster!")


def delete_storage_bucket(bucket_name: str, project_id: str):
Expand All @@ -187,20 +145,12 @@ def delete_storage_bucket(bucket_name: str, project_id: str):
)
return

client = storage.Client(project=project_id)
bucket = client.get_bucket(bucket_name)
try:
subprocess.check_call(
[
"gsutil",
"-m",
"rm",
"-r",
f"gs://{bucket_name}",
"-p",
project_id,
]
)
bucket.delete()
print(f"Successfully deleted bucket {bucket_name}.")
except subprocess.CalledProcessError as e:
except storage.exceptions.BucketNotFoundError as e:
print(f"Failed to delete bucket {bucket_name}. Error: {e}")


Expand All @@ -213,22 +163,12 @@ def delete_service_account(service_account_name: str, project_id: str):
f"Service account {service_account_name} does not exist in project {project_id}. Exiting gracefully."
)
return

client = iam_credentials_v1.IAMCredentialsClient()
client.service_account_path(project_id, service_account_name)
try:
subprocess.check_call(
[
"gcloud",
"iam",
"service-accounts",
"delete",
service_account_name,
"--quiet",
"--project",
project_id,
]
)
client.delete_service_account(service_account_name)
print(f"Successfully deleted service account {service_account_name}.")
except subprocess.CalledProcessError as e:
except iam_credentials_v1.exceptions.IamServiceAccountNotFoundError as e:
print(f"Failed to delete service account {service_account_name}. Error: {e}")


Expand Down
Loading