mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2026-08-17 16:34:44 -05:00
On packaged Linux installs the bundled venv is created with --system-site-packages (issue #7173) so it can reach system packages such as dbus-python. That also exposes the deprecated system oauth2client and whatever pyOpenSSL ships alongside it. googleapiclient imports oauth2client optionally, and on Ubuntu 24.04 that drags in a pyOpenSSL too old for our bundled cryptography, aborting startup at blueprint registration with "AttributeError: module 'lib' has no attribute 'GEN_EMAIL'". pgAdmin only ever authenticates to Google via google-auth and google-auth-oauthlib, so park a None sentinel under sys.modules['oauth2client'] before importing googleapiclient in both the web module and the standalone pgacloud provider. The optional import then fails cleanly and googleapiclient falls back to google-auth. Closes #10110
681 lines
25 KiB
Python
681 lines
25 KiB
Python
# ##########################################################################
|
|
# #
|
|
# # pgAdmin 4 - PostgreSQL Tools
|
|
# #
|
|
# # Copyright (C) 2013 - 2026, The pgAdmin Development Team
|
|
# # This software is released under the PostgreSQL Licence
|
|
# #
|
|
# ##########################################################################
|
|
|
|
# Google Cloud Deployment Implementation
|
|
import json
|
|
import os
|
|
import sys
|
|
from urllib.parse import unquote
|
|
|
|
from config import root
|
|
from pgadmin.utils.csrf import pgCSRFProtect
|
|
from pgadmin.utils.ajax import plain_text_response, unauthorized, \
|
|
make_json_response, bad_request
|
|
from pgadmin.misc.bgprocess import BatchProcess
|
|
from pgadmin.misc.cloud.utils import _create_server, CloudProcessDesc
|
|
from pgadmin.utils import PgAdminModule, filename_with_file_manager_path
|
|
from pgadmin.utils.text_sanitize import sanitize_external_text
|
|
from pgadmin.user_login_check import pga_login_required
|
|
from flask import session, current_app, request
|
|
from flask_babel import gettext as _
|
|
|
|
from oauthlib.oauth2 import AccessDeniedError
|
|
|
|
# pgAdmin only authenticates to Google via google-auth and
|
|
# google-auth-oauthlib, never the long-deprecated oauth2client.
|
|
# googleapiclient still tries to import oauth2client optionally, and on
|
|
# packaged installs the venv inherits the system site-packages
|
|
# (--system-site-packages, see issue #7173) where a stale oauth2client and
|
|
# an incompatible pyOpenSSL may be present. That optional import drags in
|
|
# the broken pyOpenSSL and aborts startup with "module 'lib' has no
|
|
# attribute 'GEN_EMAIL'". Blocking the module here makes googleapiclient
|
|
# fall back to google-auth cleanly. See issue #10110.
|
|
sys.modules.setdefault('oauth2client', None)
|
|
|
|
from googleapiclient import discovery
|
|
from googleapiclient.errors import HttpError
|
|
from google_auth_oauthlib.flow import InstalledAppFlow
|
|
from google.auth.transport.requests import Request
|
|
|
|
MODULE_NAME = 'google'
|
|
os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # Required for Oauth2
|
|
|
|
|
|
class GooglePostgresqlModule(PgAdminModule):
|
|
"""Cloud module to deploy on Google Cloud"""
|
|
|
|
def get_exposed_url_endpoints(self):
|
|
return ['google.verify_credentials',
|
|
'google.projects',
|
|
'google.regions',
|
|
'google.database_versions',
|
|
'google.instance_types',
|
|
'google.availability_zones',
|
|
'google.verification_ack',
|
|
'google.callback']
|
|
|
|
|
|
blueprint = GooglePostgresqlModule(MODULE_NAME, __name__,
|
|
static_url_path='/misc/cloud/google')
|
|
|
|
|
|
def _get_google_from_session():
|
|
"""Build a Google instance from `session['google']['state']`.
|
|
|
|
Returns None when no Google state has been seeded yet — callers should
|
|
treat that as "auth not yet started."
|
|
|
|
The previous implementation persisted a live Google instance via an
|
|
unsafe serializer, which represented an insecure-deserialization
|
|
vector. We now store only a JSON-safe dict and rebuild on demand.
|
|
"""
|
|
google = session.get('google')
|
|
if not google:
|
|
return None
|
|
state = google.get('state')
|
|
if not state:
|
|
return None
|
|
return Google.from_state(state)
|
|
|
|
|
|
def _save_google_to_session(google_obj):
|
|
"""Persist the given Google instance's state into the session."""
|
|
if 'google' not in session:
|
|
session['google'] = {}
|
|
session['google']['state'] = google_obj.to_state()
|
|
|
|
|
|
@blueprint.route("/")
|
|
@pga_login_required
|
|
def index():
|
|
return bad_request(errormsg=_("This URL cannot be called directly."))
|
|
|
|
|
|
@blueprint.route('/verify_credentials/',
|
|
methods=['POST'], endpoint='verify_credentials')
|
|
@pga_login_required
|
|
def verify_credentials():
|
|
"""
|
|
Initiate process of authorisation for google oauth2
|
|
"""
|
|
data = json.loads(request.data)
|
|
client_secret_path = data['secret']['client_secret_file'] if \
|
|
'client_secret_file' in data['secret'] else None
|
|
status = False
|
|
error = None
|
|
res_data = {}
|
|
|
|
client_secret_path = unquote(client_secret_path)
|
|
try:
|
|
client_secret_path = \
|
|
filename_with_file_manager_path(client_secret_path)
|
|
except PermissionError as e:
|
|
return unauthorized(errormsg=sanitize_external_text(str(e)))
|
|
except Exception as e:
|
|
return bad_request(errormsg=sanitize_external_text(str(e)))
|
|
|
|
if client_secret_path and os.path.exists(client_secret_path):
|
|
with open(client_secret_path, 'r') as json_file:
|
|
client_config = json.load(json_file)
|
|
|
|
if 'google' not in session:
|
|
session['google'] = {}
|
|
|
|
# Reuse cached Google when client_config hasn't changed; otherwise
|
|
# start fresh (e.g., the user picked a different secret file).
|
|
cached_google = _get_google_from_session()
|
|
if cached_google is not None and \
|
|
session['google'].get('client_config') == client_config:
|
|
_google = cached_google
|
|
else:
|
|
_google = Google(client_config)
|
|
|
|
# get auth url
|
|
host_url = request.origin + '/'
|
|
if request.root_path != '':
|
|
host_url = host_url + request.root_path + '/'
|
|
|
|
auth_url, error_msg = _google.get_auth_url(host_url)
|
|
if error_msg:
|
|
error = error_msg
|
|
else:
|
|
status = True
|
|
res_data = {'auth_url': auth_url}
|
|
session['google']['client_config'] = client_config
|
|
_save_google_to_session(_google)
|
|
else:
|
|
error = 'Client secret path not found'
|
|
session.pop('google', None)
|
|
|
|
return make_json_response(
|
|
success=status,
|
|
errormsg=sanitize_external_text(error),
|
|
data=res_data)
|
|
|
|
|
|
@blueprint.route('/callback',
|
|
methods=['GET'], endpoint='callback')
|
|
@pgCSRFProtect.exempt
|
|
@pga_login_required
|
|
def callback():
|
|
"""
|
|
Call back function on google authentication response.
|
|
:return:
|
|
"""
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is None:
|
|
return plain_text_response(
|
|
'Authentication session expired. Please try again.')
|
|
res = google_obj.callback(request)
|
|
_save_google_to_session(google_obj)
|
|
return plain_text_response(res)
|
|
|
|
|
|
@blueprint.route('/verification_ack',
|
|
methods=['GET'], endpoint='verification_ack')
|
|
@pga_login_required
|
|
def verification_ack():
|
|
"""
|
|
Checks for google oauth2 authorisation confirmation
|
|
:return:
|
|
"""
|
|
verified = False
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is not None:
|
|
verified, error = google_obj.verification_ack()
|
|
_save_google_to_session(google_obj)
|
|
return make_json_response(
|
|
success=verified, errormsg=sanitize_external_text(error))
|
|
else:
|
|
return make_json_response(success=verified,
|
|
errormsg='Authentication is failed.')
|
|
|
|
|
|
@blueprint.route('/projects/',
|
|
methods=['GET'], endpoint='projects')
|
|
@pga_login_required
|
|
def get_projects():
|
|
"""
|
|
Lists the projects for authorized user
|
|
:return: list of projects
|
|
"""
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is not None:
|
|
projects_list, error = google_obj.get_projects()
|
|
if error:
|
|
return bad_request(errormsg=sanitize_external_text(error))
|
|
return make_json_response(data=projects_list)
|
|
|
|
|
|
@blueprint.route('/regions/<project_id>',
|
|
methods=['GET'], endpoint='regions')
|
|
@pga_login_required
|
|
def get_regions(project_id):
|
|
"""
|
|
Lists regions based on project for authorized user
|
|
:param project_id: google project id
|
|
:return: google cloud sql region list
|
|
"""
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is not None and project_id:
|
|
regions_list, error = google_obj.get_regions(project_id)
|
|
_save_google_to_session(google_obj)
|
|
if error:
|
|
return bad_request(errormsg=sanitize_external_text(error))
|
|
return make_json_response(data=regions_list)
|
|
else:
|
|
return make_json_response(data=[])
|
|
|
|
|
|
@blueprint.route('/availability_zones/<region>',
|
|
methods=['GET'], endpoint='availability_zones')
|
|
@pga_login_required
|
|
def get_availability_zones(region):
|
|
"""
|
|
List availability zones for specified region
|
|
:param region: google region
|
|
:return: google cloud sql availability zone list
|
|
"""
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is not None and region:
|
|
availability_zone_list = google_obj.get_availability_zones(region)
|
|
return make_json_response(data=availability_zone_list)
|
|
else:
|
|
return make_json_response(data=[])
|
|
|
|
|
|
@blueprint.route('/instance_types/<project_id>/<region>/<instance_class>',
|
|
methods=['GET'], endpoint='instance_types')
|
|
@pga_login_required
|
|
def get_instance_types(project_id, region, instance_class):
|
|
"""
|
|
List the instances types for specified google project, region &
|
|
instance type
|
|
:param project_id: google project id
|
|
:param region: google cloud region
|
|
:param instance_class: google cloud sql instnace class
|
|
:return:
|
|
"""
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is not None and project_id and region:
|
|
instance_types_dict = google_obj.get_instance_types(
|
|
project_id, region)
|
|
instance_types_list, error = (
|
|
instance_types_dict.get(instance_class, []))
|
|
if error:
|
|
return bad_request(errormsg=sanitize_external_text(error))
|
|
return make_json_response(data=instance_types_list)
|
|
else:
|
|
return make_json_response(data=[])
|
|
|
|
|
|
@blueprint.route('/database_versions/',
|
|
methods=['GET'], endpoint='database_versions')
|
|
@pga_login_required
|
|
def get_database_versions():
|
|
"""
|
|
Lists the postgresql database versions.
|
|
:return: PostgreSQL version list
|
|
"""
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is not None:
|
|
db_version_list, error = google_obj.get_database_versions()
|
|
if error:
|
|
return bad_request(errormsg=sanitize_external_text(error))
|
|
return make_json_response(data=db_version_list)
|
|
else:
|
|
return make_json_response(data=[])
|
|
|
|
|
|
def deploy_on_google(data):
|
|
"""Deploy the Postgres instance on RDS."""
|
|
_cmd = 'python'
|
|
_cmd_script = '{0}/pgacloud/pgacloud.py'.format(root)
|
|
_label = data['instance_details']['name']
|
|
|
|
# Supported arguments for google cloud sql deployment
|
|
args = [_cmd_script,
|
|
data['cloud'],
|
|
'create-instance',
|
|
|
|
'--project', data['instance_details']['project'],
|
|
|
|
'--region', data['instance_details']['region'],
|
|
|
|
'--name', data['instance_details']['name'],
|
|
|
|
'--db-version', data['instance_details']['db_version'],
|
|
|
|
'--instance-type', data['instance_details']['instance_type'],
|
|
|
|
'--storage-type', data['instance_details']['storage_type'],
|
|
|
|
'--storage-size', str(data['instance_details']['storage_size']),
|
|
|
|
'--public-ip', str(data['instance_details']['public_ips']),
|
|
|
|
'--availability-zone',
|
|
data['instance_details']['availability_zone'],
|
|
|
|
'--high-availability',
|
|
str(data['instance_details']['high_availability']),
|
|
|
|
'--secondary-availability-zone',
|
|
data['instance_details']['secondary_availability_zone'],
|
|
]
|
|
|
|
_cmd_msg = '{0} {1} {2}'.format(_cmd, _cmd_script, ' '.join(args))
|
|
try:
|
|
sid = _create_server({
|
|
'gid': data['db_details']['gid'],
|
|
'name': data['instance_details']['name'],
|
|
'db': 'postgres',
|
|
'username': 'postgres',
|
|
'port': 5432,
|
|
'cloud_status': -1
|
|
})
|
|
|
|
p = BatchProcess(
|
|
desc=CloudProcessDesc(sid, _cmd_msg, data['cloud'],
|
|
data['instance_details']['name']),
|
|
cmd=_cmd,
|
|
args=args
|
|
)
|
|
|
|
# Set env variables for background process of deployment
|
|
env = dict()
|
|
google_obj = _get_google_from_session()
|
|
if google_obj is None or google_obj.credentials_json is None:
|
|
return False, None, 'Google credentials missing from session.'
|
|
env['GOOGLE_CREDENTIALS'] = json.dumps(google_obj.credentials_json)
|
|
|
|
if 'db_password' in data['db_details']:
|
|
env['GOOGLE_DATABASE_PASSWORD'] = data['db_details']['db_password']
|
|
|
|
p.set_env_variables(None, env=env)
|
|
p.update_server_id(p.id, sid)
|
|
p.start()
|
|
|
|
return True, p, {'label': _label, 'sid': sid}
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return False, None, str(e)
|
|
|
|
|
|
def clear_google_session():
|
|
"""Clear Google Session"""
|
|
if 'google' in session:
|
|
session.pop('google')
|
|
|
|
|
|
class Google:
|
|
def __init__(self, client_config=None):
|
|
# Google cloud sql api versions
|
|
self._cloud_resource_manager_api_version = 'v1'
|
|
self._sqladmin_api_version = 'v1'
|
|
self._compute_api_version = 'v1'
|
|
|
|
# Scope required for google cloud sql deployment
|
|
self._scopes = ['https://www.googleapis.com/auth/cloud-platform',
|
|
'https://www.googleapis.com/auth/sqlservice.admin']
|
|
|
|
# Instance classed
|
|
self._instance_classes = [{'label': 'Standard', 'value': 'standard'},
|
|
{'label': 'High Memory', 'value': 'highmem'},
|
|
{'label': 'Shared', 'value': 'shared'}]
|
|
|
|
self._client_config = client_config
|
|
self._credentials = None
|
|
self.credentials_json = None
|
|
self._project_id = None
|
|
self._regions = []
|
|
self._availability_zones = {}
|
|
self._verification_successful = False
|
|
self._verification_error = None
|
|
self._redirect_url = None
|
|
|
|
def to_state(self):
|
|
"""Serialize the persistable state of this Google instance to a
|
|
plain dict for storage in `flask.session`.
|
|
|
|
Live SDK objects (like `_credentials`) are intentionally NOT
|
|
included — they are rebuilt from `credentials_json` in
|
|
`from_state()`. Storing only data primitives lets the session
|
|
layer use a safe (non-binary-serializer) format.
|
|
"""
|
|
return {
|
|
'client_config': self._client_config,
|
|
'credentials_json': self.credentials_json,
|
|
'redirect_url': self._redirect_url,
|
|
'project_id': self._project_id,
|
|
'regions': self._regions,
|
|
'availability_zones': self._availability_zones,
|
|
'verification_successful': self._verification_successful,
|
|
'verification_error': self._verification_error,
|
|
}
|
|
|
|
@classmethod
|
|
def from_state(cls, state):
|
|
"""Rebuild a Google instance from a previously-serialized dict.
|
|
|
|
The reverse of `to_state()`. The Google SDK credentials object is
|
|
reconstructed from the stored token dict so subsequent API calls
|
|
work without forcing the user back through the OAuth2 flow.
|
|
"""
|
|
if not isinstance(state, dict):
|
|
return None
|
|
obj = cls(client_config=state.get('client_config'))
|
|
obj.credentials_json = state.get('credentials_json')
|
|
if obj.credentials_json:
|
|
# Rebuild google.oauth2.credentials.Credentials from the
|
|
# persisted token dict. Credentials() ignores unknown kwargs,
|
|
# so id_token (kept in the dict for API parity) is dropped.
|
|
from google.oauth2.credentials import Credentials
|
|
obj._credentials = Credentials(
|
|
token=obj.credentials_json.get('token'),
|
|
refresh_token=obj.credentials_json.get('refresh_token'),
|
|
token_uri=obj.credentials_json.get('token_uri'),
|
|
client_id=obj.credentials_json.get('client_id'),
|
|
client_secret=obj.credentials_json.get('client_secret'),
|
|
scopes=obj.credentials_json.get('scopes'),
|
|
)
|
|
obj._redirect_url = state.get('redirect_url')
|
|
obj._project_id = state.get('project_id')
|
|
obj._regions = state.get('regions', []) or []
|
|
obj._availability_zones = state.get('availability_zones', {}) or {}
|
|
obj._verification_successful = bool(
|
|
state.get('verification_successful', False))
|
|
obj._verification_error = state.get('verification_error')
|
|
return obj
|
|
|
|
def get_auth_url(self, host_url):
|
|
"""
|
|
Provides google authorisation url
|
|
:param host_url: Base url for hosting application
|
|
:return: authorisation url to complete authentication
|
|
"""
|
|
auth_url = None
|
|
error = None
|
|
# reset below variable to get latest values in fresh
|
|
# authentication call
|
|
self._verification_successful = False
|
|
self._verification_error = None
|
|
try:
|
|
self._redirect_url = host_url + 'google/callback'
|
|
flow = InstalledAppFlow.from_client_config(
|
|
client_config=self._client_config, scopes=self._scopes,
|
|
redirect_uri=self._redirect_url)
|
|
auth_url, state = flow.authorization_url(
|
|
prompt='select_account', access_type='offline',
|
|
include_granted_scopes='true')
|
|
session["state"] = state
|
|
except Exception as e:
|
|
error = str(e)
|
|
self._verification_error = error
|
|
return auth_url, error
|
|
|
|
def callback(self, flask_request):
|
|
"""
|
|
Callback function on completion of google authorisation request
|
|
:param flask_request:
|
|
:return: Success or error message
|
|
"""
|
|
try:
|
|
authorization_response = flask_request.url
|
|
if session['state'] != flask_request.args.get('state', None):
|
|
self._verification_successful = False,
|
|
self._verification_error = 'Invalid state parameter'
|
|
flow = InstalledAppFlow.from_client_config(
|
|
client_config=self._client_config, scopes=self._scopes,
|
|
redirect_uri=self._redirect_url)
|
|
flow.fetch_token(authorization_response=authorization_response)
|
|
self._credentials = flow.credentials
|
|
self.credentials_json = \
|
|
self._credentials_to_dict(self._credentials)
|
|
self._verification_successful = True
|
|
return 'The authentication flow has completed. ' \
|
|
'This window will be closed.'
|
|
except AccessDeniedError as er:
|
|
self._verification_successful = False
|
|
self._verification_error = er.error
|
|
if self._verification_error == 'access_denied':
|
|
self._verification_error = 'Access denied.'
|
|
return self._verification_error
|
|
|
|
@staticmethod
|
|
def _credentials_to_dict(credentials):
|
|
return {'token': credentials.token,
|
|
'refresh_token': credentials.refresh_token,
|
|
'token_uri': credentials.token_uri,
|
|
'client_id': credentials.client_id,
|
|
'client_secret': credentials.client_secret,
|
|
'scopes': credentials.scopes,
|
|
'id_token': credentials.id_token}
|
|
|
|
def verification_ack(self):
|
|
"""Check the Verification is done or not."""
|
|
return self._verification_successful, self._verification_error
|
|
|
|
def _get_credentials(self, scopes):
|
|
"""
|
|
Provides google credentials for google cloud sql api calls
|
|
:param scopes: Required scope of credentials
|
|
:return: google credential object
|
|
"""
|
|
if not self._credentials or not self._credentials.valid:
|
|
if self._credentials and self._credentials.expired and \
|
|
self._credentials.refresh_token and \
|
|
self._credentials.has_scopes(scopes):
|
|
self._credentials.refresh(Request())
|
|
return self._credentials
|
|
return self._credentials
|
|
|
|
def get_projects(self):
|
|
"""
|
|
List the google projects for authorised user
|
|
:return:
|
|
"""
|
|
projects = []
|
|
error = None
|
|
credentials = self._get_credentials(self._scopes)
|
|
service = discovery.build('cloudresourcemanager',
|
|
self._cloud_resource_manager_api_version,
|
|
credentials=credentials)
|
|
try:
|
|
req = service.projects().list()
|
|
res = req.execute()
|
|
for project in res.get('projects', []):
|
|
projects.append({'label': project['projectId'],
|
|
'value': project['projectId']})
|
|
except HttpError as e:
|
|
error = e.reason
|
|
except Exception as e:
|
|
error = str(e)
|
|
return projects, error
|
|
|
|
def get_regions(self, project):
|
|
"""
|
|
List regions for specified google cloud project
|
|
:param project: google cloud project id.
|
|
:return:
|
|
"""
|
|
self._project_id = project
|
|
credentials = self._get_credentials(self._scopes)
|
|
service = discovery.build('compute',
|
|
self._compute_api_version,
|
|
credentials=credentials)
|
|
error = None
|
|
try:
|
|
req = service.regions().list(project=project)
|
|
res = req.execute()
|
|
for item in res.get('items', []):
|
|
region_name = item['name']
|
|
self._regions.append(
|
|
{'label': region_name, 'value': region_name})
|
|
region_zones = item.get('zones', [])
|
|
region_zones = list(
|
|
map(lambda region: region.split('/')[-1], region_zones))
|
|
self._availability_zones[region_name] = region_zones
|
|
except HttpError as e:
|
|
error = e.reason
|
|
except Exception as e:
|
|
error = str(e)
|
|
return self._regions, error
|
|
|
|
def get_availability_zones(self, region):
|
|
"""
|
|
List availability zones in given google cloud region
|
|
:param region: google cloud region
|
|
:return:
|
|
"""
|
|
az_list = []
|
|
for az in self._availability_zones.get(region, []):
|
|
az_list.append({'label': az, 'value': az})
|
|
return az_list
|
|
|
|
def get_instance_types(self, project, region):
|
|
"""
|
|
Lists google cloud sql instance types.
|
|
:param project:
|
|
:param region:
|
|
:return:
|
|
"""
|
|
standard_instances = []
|
|
shared_instances = []
|
|
high_mem = []
|
|
instance_types = {}
|
|
error = None
|
|
credentials = self._get_credentials(self._scopes)
|
|
service = discovery.build('sqladmin',
|
|
self._sqladmin_api_version,
|
|
credentials=credentials)
|
|
try:
|
|
req = service.tiers().list(project=project)
|
|
res = req.execute()
|
|
for item in res.get('items', []):
|
|
if region in item.get('region', []):
|
|
if item['tier'].find('standard') != -1:
|
|
vcpu = item['tier'].split('-')[-1]
|
|
mem = round(int(item['RAM']) / (1024 * 1024))
|
|
label = (
|
|
vcpu + ' vCPU, ' + str(round(mem / 1024)) + ' GB')
|
|
value = 'db-custom-' + str(vcpu) + '-' + str(mem)
|
|
standard_instances.append(
|
|
{'label': label, 'value': value})
|
|
elif item['tier'].find('highmem') != -1:
|
|
vcpu = item['tier'].split('-')[-1]
|
|
mem = round(int(item['RAM']) / (1024 * 1024))
|
|
label = \
|
|
(vcpu + ' vCPU, ' + str(round(mem / 1024)) + ' GB')
|
|
value = 'db-custom-' + str(vcpu) + '-' + str(mem)
|
|
high_mem.append(
|
|
{'label': label, 'value': value})
|
|
else:
|
|
label = '1 vCPU, ' + str(
|
|
round((int(item['RAM']) / 1073741824), 2)) + ' GB'
|
|
value = item['tier']
|
|
shared_instances.append(
|
|
{'label': label, 'value': value})
|
|
instance_types = {'standard': standard_instances,
|
|
'highmem': high_mem,
|
|
'shared': shared_instances}
|
|
except HttpError as e:
|
|
error = e.reason
|
|
except Exception as e:
|
|
error = str(e)
|
|
return instance_types, error
|
|
|
|
def get_database_versions(self):
|
|
"""
|
|
Lists the PostgreSQL database versions
|
|
:return:
|
|
"""
|
|
pg_database_versions = []
|
|
database_versions = []
|
|
error = None
|
|
credentials = self._get_credentials(self._scopes)
|
|
service = discovery.build('sqladmin',
|
|
self._sqladmin_api_version,
|
|
credentials=credentials)
|
|
try:
|
|
req = service.flags().list()
|
|
res = req.execute()
|
|
for item in res.get('items', []):
|
|
if item.get('name', '') == 'max_parallel_workers':
|
|
pg_database_versions = item.get('appliesTo', [])
|
|
for version in pg_database_versions:
|
|
label = (version.title().split('_')[0])[0:7] \
|
|
+ 'SQL ' + version.split('_')[1]
|
|
database_versions.append({'label': label, 'value': version})
|
|
except HttpError as e:
|
|
error = e.reason
|
|
except Exception as e:
|
|
error = str(e)
|
|
return database_versions, error
|