mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-01-24 15:26:46 -06:00
433 lines
16 KiB
Python
433 lines
16 KiB
Python
# ##########################################################################
|
|
# #
|
|
# # pgAdmin 4 - PostgreSQL Tools
|
|
# #
|
|
# # Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
|
# # This software is released under the PostgreSQL Licence
|
|
# #
|
|
# ##########################################################################
|
|
|
|
# EDB BigAnimal Cloud Deployment Implementation
|
|
|
|
import requests
|
|
import json
|
|
import pickle
|
|
from flask_babel import gettext
|
|
import simplejson as json
|
|
from flask import session, current_app
|
|
from flask_security import login_required
|
|
from werkzeug.datastructures import Headers
|
|
from pgadmin.utils import PgAdminModule
|
|
from pgadmin.misc.cloud.utils import _create_server, CloudProcessDesc
|
|
from pgadmin.misc.bgprocess.processes import BatchProcess
|
|
from pgadmin.utils.ajax import make_json_response,\
|
|
internal_server_error, bad_request, success_return
|
|
from config import root
|
|
from pgadmin.utils.constants import MIMETYPE_APP_JSON
|
|
|
|
MODULE_NAME = 'biganimal'
|
|
|
|
|
|
class BigAnimalModule(PgAdminModule):
|
|
"""Cloud module to deploy on EDB BigAnimal"""
|
|
def get_own_stylesheets(self):
|
|
"""
|
|
Returns:
|
|
list: the stylesheets used by this module.
|
|
"""
|
|
stylesheets = []
|
|
return stylesheets
|
|
|
|
def get_exposed_url_endpoints(self):
|
|
return ['biganimal.verification',
|
|
'biganimal.verification_ack',
|
|
'biganimal.regions',
|
|
'biganimal.db_types',
|
|
'biganimal.db_versions',
|
|
'biganimal.instance_types',
|
|
'biganimal.volume_types',
|
|
'biganimal.volume_properties']
|
|
|
|
|
|
blueprint = BigAnimalModule(MODULE_NAME, __name__,
|
|
static_url_path='/misc/cloud/biganimal')
|
|
|
|
|
|
@blueprint.route('/verification_ack/',
|
|
methods=['GET'], endpoint='verification_ack')
|
|
@login_required
|
|
def biganimal_verification_ack():
|
|
"""Check the Verification is done or not."""
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
status, error = biganimal_obj.polling_for_token()
|
|
session['biganimal']['provider_obj'] = pickle.dumps(biganimal_obj, -1)
|
|
return make_json_response(success=status,
|
|
errormsg=error)
|
|
|
|
|
|
@blueprint.route('/verification/',
|
|
methods=['GET'], endpoint='verification')
|
|
@login_required
|
|
def verification():
|
|
"""Verify Credentials."""
|
|
biganimal = BigAnimalProvider()
|
|
verification_uri = biganimal.get_device_code()
|
|
session['biganimal'] = {}
|
|
session['biganimal']['provider_obj'] = pickle.dumps(biganimal, -1)
|
|
|
|
return make_json_response(data=verification_uri)
|
|
|
|
|
|
@blueprint.route('/regions/',
|
|
methods=['GET'], endpoint='regions')
|
|
@login_required
|
|
def biganimal_regions():
|
|
"""Get Regions."""
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
status, regions = biganimal_obj.get_regions()
|
|
return make_json_response(data=regions)
|
|
|
|
|
|
@blueprint.route('/db_types/',
|
|
methods=['GET'], endpoint='db_types')
|
|
@login_required
|
|
def biganimal_db_types():
|
|
"""Get Database Types."""
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
pg_types = biganimal_obj.get_postgres_types()
|
|
return make_json_response(data=pg_types)
|
|
|
|
|
|
@blueprint.route('/db_versions/',
|
|
methods=['GET'], endpoint='db_versions')
|
|
@login_required
|
|
def biganimal_db_versions():
|
|
"""Get Database Version."""
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
pg_versions = biganimal_obj.get_postgres_versions()
|
|
return make_json_response(data=pg_versions)
|
|
|
|
|
|
@blueprint.route('/instance_types/<region_id>',
|
|
methods=['GET'], endpoint='instance_types')
|
|
@login_required
|
|
def biganimal_instance_types(region_id):
|
|
"""Get Instance Types."""
|
|
if not region_id:
|
|
return make_json_response(data=[])
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
biganimal_instances = biganimal_obj.get_instance_types(region_id)
|
|
return make_json_response(data=biganimal_instances)
|
|
|
|
|
|
@blueprint.route('/volume_types/<region_id>',
|
|
methods=['GET'], endpoint='volume_types')
|
|
@login_required
|
|
def biganimal_volume_types(region_id):
|
|
"""Get Volume Types."""
|
|
if not region_id:
|
|
return make_json_response(data=[])
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
biganimal_volumes = biganimal_obj.get_volume_types(region_id)
|
|
return make_json_response(data=biganimal_volumes)
|
|
|
|
|
|
@blueprint.route('/volume_properties/<region_id>/<volume_type>',
|
|
methods=['GET'], endpoint='volume_properties')
|
|
@login_required
|
|
def biganimal_volume_properties(region_id, volume_type):
|
|
"""Get Volume Properties."""
|
|
if not region_id:
|
|
return make_json_response(data=[])
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
biganimal_volume_properties = biganimal_obj.get_volume_properties(
|
|
region_id,
|
|
volume_type)
|
|
return make_json_response(data=biganimal_volume_properties)
|
|
|
|
|
|
class BigAnimalProvider():
|
|
"""BigAnimal provider class"""
|
|
BASE_URL = 'https://portal.biganimal.com/api/v1'
|
|
|
|
def __init__(self):
|
|
self.provider = {}
|
|
self.device_code = {}
|
|
self.token = {}
|
|
self.raw_access_token = None
|
|
self.access_token = None
|
|
self.token_error = {}
|
|
self.token_status = -1
|
|
self.get_auth_provider()
|
|
|
|
def _get_headers(self):
|
|
return {
|
|
'content-type': MIMETYPE_APP_JSON,
|
|
'Authorization': 'Bearer {0}'.format(self.access_token)
|
|
}
|
|
|
|
def get_auth_provider(self):
|
|
"""Get Authentication Provider Relevant Information."""
|
|
provider_resp = requests.get("{0}/{1}".format(self.BASE_URL,
|
|
'auth/provider'))
|
|
if provider_resp.status_code == 200 and provider_resp.content:
|
|
self.provider = json.loads(provider_resp.content)
|
|
|
|
def get_device_code(self):
|
|
"""Get device code"""
|
|
_url = "{0}/{1}".format(self.provider['issuerUri'],
|
|
'oauth/device/code')
|
|
_headers = {"content-type": "application/x-www-form-urlencoded"}
|
|
_data = {
|
|
'client_id': self.provider['clientId'],
|
|
'audience': self.provider['audience'],
|
|
'scope': self.provider['scope']
|
|
}
|
|
device_resp = requests.post(_url,
|
|
headers=_headers,
|
|
data=_data)
|
|
|
|
if device_resp.status_code == 200 and device_resp.content:
|
|
self.device_code = json.loads(device_resp.content)
|
|
return self.device_code['verification_uri_complete']
|
|
|
|
def polling_for_token(self):
|
|
# Polling for the Token
|
|
_url = "{0}/{1}".format(self.provider['issuerUri'], 'oauth/token')
|
|
_headers = {"content-type": "application/x-www-form-urlencoded"}
|
|
_data = {
|
|
'grant_type': 'urn:ietf:params:oauth:grant-type:device_code',
|
|
'device_code': self.device_code['device_code'],
|
|
'client_id': self.provider['clientId']
|
|
}
|
|
token_resp = requests.post(_url,
|
|
headers=_headers,
|
|
data=_data)
|
|
if token_resp.status_code == 200:
|
|
self.token = json.loads(token_resp.content)
|
|
self.raw_access_token = self.token['access_token']
|
|
self.token_error['error'] = None
|
|
self.token_status = 1
|
|
status, msg = self.exchange_token()
|
|
if status and not self._check_admin_permission():
|
|
return False, gettext('forbidden')
|
|
return status, msg
|
|
elif token_resp.status_code == 403:
|
|
self.token_error = json.loads(token_resp.content)
|
|
if self.token_error['error'] == 'authorization_pending' or\
|
|
self.token_error['error'] == 'access_denied':
|
|
self.token_status = 0
|
|
return False, self.token_error['error']
|
|
return False, None
|
|
|
|
def exchange_token(self):
|
|
_url = "{0}/{1}".format(self.BASE_URL, 'auth/token')
|
|
_headers = {"content-type": "application/json"}
|
|
_data = {'token': self.raw_access_token}
|
|
token_resp = requests.post(_url,
|
|
headers=_headers,
|
|
data=json.dumps(_data))
|
|
|
|
final_token = json.loads(token_resp.content)
|
|
if token_resp.status_code == 200:
|
|
self.access_token = final_token['token']
|
|
return True, None
|
|
else:
|
|
return False, self.token_error['error']
|
|
|
|
def _check_admin_permission(self):
|
|
"""
|
|
Check wehether the user has valid role or not.
|
|
There is no direct way to do this, so just checking the create cluster
|
|
permission.
|
|
"""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'admin/permissions')
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code != 200:
|
|
return False
|
|
if resp.status_code == 200 and resp.content:
|
|
content = json.loads(resp.content)
|
|
if 'permissionsList' in content and 'create:clusters' in content[
|
|
'permissionsList']:
|
|
return True
|
|
return False
|
|
|
|
def get_regions(self):
|
|
"""Get regions"""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'cloud-providers/azure/regions')
|
|
regions = []
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code == 200 and resp.content:
|
|
regions_resp = json.loads(resp.content)
|
|
for value in regions_resp['regionsList']:
|
|
regions.append({
|
|
'label': value['regionName'],
|
|
'value': value['regionId']
|
|
})
|
|
return True, regions
|
|
elif resp.content:
|
|
regions_resp = json.loads(resp.content)
|
|
return False, regions_resp['error']['message']
|
|
else:
|
|
return False, gettext('Error retrieving regions.')
|
|
|
|
def get_postgres_types(self):
|
|
"""Get Postgres Types."""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'postgres-types')
|
|
pg_types = []
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code == 200 and resp.content:
|
|
pg_types_resp = json.loads(resp.content)
|
|
for value in pg_types_resp['pgTypesList']:
|
|
pg_types.append({
|
|
'label': value['name'],
|
|
'value': value['id']
|
|
})
|
|
return pg_types
|
|
|
|
def get_postgres_versions(self):
|
|
"""Get Postgres Versions."""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'postgres-versions')
|
|
pg_versions = []
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code == 200 and resp.content:
|
|
pg_versions_resp = json.loads(resp.content)
|
|
for value in pg_versions_resp['pgVersionsList']:
|
|
pg_versions.append({
|
|
'label': value['versionName'],
|
|
'value': value['versionId']
|
|
})
|
|
return pg_versions
|
|
|
|
def get_instance_types(self, region_id):
|
|
"""GEt Instance Types."""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'cloud-providers/azure/regions/'
|
|
'{0}/instance-types'.format(region_id))
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code == 200 and resp.content:
|
|
pg_types = json.loads(resp.content)
|
|
return pg_types['instanceTypesList']
|
|
return []
|
|
|
|
def get_volume_types(self, region_id):
|
|
"""Get Volume Types."""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'cloud-providers/azure/regions/{0}/volume-types'.format(region_id))
|
|
volume_types = []
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code == 200 and resp.content:
|
|
volume_resp = json.loads(resp.content)
|
|
for value in volume_resp['volumeTypesList']:
|
|
volume_types.append({
|
|
'label': value['displayName'],
|
|
'value': value['id']
|
|
})
|
|
return volume_types
|
|
|
|
def get_volume_properties(self, region_id, volume_type):
|
|
"""Get Volume Properties."""
|
|
_url = "{0}/{1}".format(
|
|
self.BASE_URL,
|
|
'cloud-providers/azure/regions/{0}/volume-types'
|
|
'/{1}/volume-properties'.format(region_id, volume_type))
|
|
volume_properties = []
|
|
resp = requests.get(_url, headers=self._get_headers())
|
|
if resp.status_code == 200 and resp.content:
|
|
volume_prop = json.loads(resp.content)
|
|
for value in volume_prop['volumePropertiesList']:
|
|
volume_properties.append({
|
|
'label': value['value'],
|
|
'value': value['id']
|
|
})
|
|
return volume_properties
|
|
|
|
|
|
def clear_biganimal_session():
|
|
"""Clear session data."""
|
|
if 'biganimal' in session:
|
|
session.pop('biganimal')
|
|
|
|
|
|
def deploy_on_biganimal(data):
|
|
"""Deploy Postgres instance on BigAnimal"""
|
|
_cmd = 'python'
|
|
_cmd_script = '{0}/pgacloud/pgacloud.py'.format(root)
|
|
_label = data['instance_details']['name']
|
|
_private_network = '1' if str(data['instance_details']['cloud_type']
|
|
) == 'private' else '0'
|
|
_instance_size = data['instance_details']['instance_size'].split('||')[1]
|
|
|
|
args = [_cmd_script,
|
|
data['cloud'],
|
|
'create-instance',
|
|
'--name',
|
|
data['instance_details']['name'],
|
|
'--region',
|
|
str(data['instance_details']['region']),
|
|
'--db-type',
|
|
str(data['db_details']['database_type']),
|
|
'--db-version',
|
|
str(data['db_details']['postgres_version']),
|
|
'--volume-type',
|
|
str(data['instance_details']['volume_type']),
|
|
'--volume-properties',
|
|
str(data['instance_details']['volume_properties']),
|
|
'--instance-type',
|
|
str(_instance_size),
|
|
'--private-network',
|
|
_private_network
|
|
]
|
|
|
|
if 'biganimal_public_ip' in data['instance_details']:
|
|
args.append('--public-ip')
|
|
args.append(str(data['instance_details']['biganimal_public_ip']))
|
|
|
|
_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': 'edb_admin',
|
|
'username': 'edb_admin',
|
|
'port': 5432,
|
|
'cloud_status': -1
|
|
})
|
|
|
|
p = BatchProcess(
|
|
desc=CloudProcessDesc(sid, _cmd_msg,
|
|
data['cloud'],
|
|
data['instance_details']['name']
|
|
),
|
|
cmd=_cmd,
|
|
args=args
|
|
)
|
|
|
|
env = dict()
|
|
biganimal_obj = pickle.loads(session['biganimal']['provider_obj'])
|
|
env['BIGANIMAL_ACCESS_KEY'] = biganimal_obj.access_token
|
|
|
|
if 'password' in data['db_details']:
|
|
env['BIGANIMAL_DATABASE_PASSWORD'] = data[
|
|
'db_details']['password']
|
|
|
|
p.set_env_variables(None, env=env)
|
|
p.update_server_id(p.id, sid)
|
|
p.start()
|
|
|
|
return True, {'label': _label, 'sid': sid}
|
|
|
|
except Exception as e:
|
|
current_app.logger.exception(e)
|
|
return False, str(e)
|