mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added capability to deploy PostgreSQL servers on EDB BigAnimal. Fixes #7179
This commit is contained in:
committed by
Akshay Joshi
parent
0795b22ae6
commit
5677b1e5f8
432
web/pgadmin/misc/cloud/biganimal/__init__.py
Normal file
432
web/pgadmin/misc/cloud/biganimal/__init__.py
Normal file
@@ -0,0 +1,432 @@
|
||||
# ##########################################################################
|
||||
# #
|
||||
# # 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 'public_ip' in data['instance_details']:
|
||||
args.append('--public-ip')
|
||||
args.append(str(data['instance_details']['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)
|
||||
Reference in New Issue
Block a user