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 Amazon RDS. Fixes #7177
This commit is contained in:
committed by
Akshay Joshi
parent
b89e306df0
commit
e61a1045f5
410
web/pgadmin/misc/cloud/__init__.py
Normal file
410
web/pgadmin/misc/cloud/__init__.py
Normal file
@@ -0,0 +1,410 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
"""Implements Cloud Deployment"""
|
||||
|
||||
import simplejson as json
|
||||
from flask import Response, url_for, session
|
||||
from flask import render_template, request, current_app
|
||||
from flask_babel import gettext
|
||||
from flask_security import login_required, current_user
|
||||
|
||||
from pgadmin.utils import PgAdminModule, html
|
||||
from pgadmin.utils.ajax import make_json_response,\
|
||||
internal_server_error, bad_request, success_return
|
||||
|
||||
from pgadmin.utils.constants import MIMETYPE_APP_JS
|
||||
from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
|
||||
from pgadmin.model import db, Server, Process
|
||||
from pgadmin.misc.cloud.utils.rds import RDS, verify_aws_credentials,\
|
||||
get_aws_db_instances, get_aws_db_versions, clear_aws_session,\
|
||||
get_aws_regions
|
||||
|
||||
from config import root
|
||||
|
||||
# set template path for sql scripts
|
||||
MODULE_NAME = 'cloud'
|
||||
server_info = {}
|
||||
|
||||
|
||||
class CloudModule(PgAdminModule):
|
||||
"""
|
||||
class CloudModule(Object):
|
||||
|
||||
It is a wizard which inherits PgAdminModule
|
||||
class and define methods to load its own
|
||||
javascript file.
|
||||
|
||||
LABEL = gettext('Browser')
|
||||
"""
|
||||
|
||||
def get_own_stylesheets(self):
|
||||
"""
|
||||
Returns:
|
||||
list: the stylesheets used by this module.
|
||||
"""
|
||||
stylesheets = []
|
||||
return stylesheets
|
||||
|
||||
def get_own_javascripts(self):
|
||||
""""
|
||||
Returns:
|
||||
list: js files used by this module
|
||||
"""
|
||||
scripts = []
|
||||
scripts.append({
|
||||
'name': 'pgadmin.misc.cloud',
|
||||
'path': url_for('cloud.index') + 'cloud',
|
||||
'when': None
|
||||
})
|
||||
scripts.append({
|
||||
'name': 'pgadmin.browser.wizard',
|
||||
'path': url_for('browser.static', filename='js/wizard'),
|
||||
'when': None
|
||||
})
|
||||
return scripts
|
||||
|
||||
def get_exposed_url_endpoints(self):
|
||||
"""
|
||||
Returns:
|
||||
list: URL endpoints for cloud module
|
||||
"""
|
||||
return ['cloud.deploy_on_cloud',
|
||||
'cloud.get_aws_db_versions',
|
||||
'cloud.verify_credentials',
|
||||
'cloud.get_aws_db_instances',
|
||||
'cloud.update_cloud_server',
|
||||
'cloud.update_cloud_process',
|
||||
'cloud.get_aws_regions']
|
||||
|
||||
|
||||
# Create blueprint for CloudModule class
|
||||
blueprint = CloudModule(
|
||||
MODULE_NAME, __name__, static_url_path='/misc/cloud')
|
||||
|
||||
|
||||
@blueprint.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
return bad_request(
|
||||
errormsg=gettext("This URL cannot be called directly.")
|
||||
)
|
||||
|
||||
|
||||
@blueprint.route("/cloud.js")
|
||||
@login_required
|
||||
def script():
|
||||
"""render own javascript"""
|
||||
res = Response(response=render_template(
|
||||
"cloud/js/cloud.js", _=gettext),
|
||||
status=200,
|
||||
mimetype=MIMETYPE_APP_JS)
|
||||
return res
|
||||
|
||||
|
||||
@blueprint.route('/verify_credentials/',
|
||||
methods=['POST'], endpoint='verify_credentials')
|
||||
@login_required
|
||||
def verify_credentials():
|
||||
"""Verify Credentials."""
|
||||
data = json.loads(request.data, encoding='utf-8')
|
||||
|
||||
status, msg = verify_aws_credentials(data)
|
||||
if status:
|
||||
msg = 'verified'
|
||||
|
||||
return make_json_response(success=status, info=msg)
|
||||
|
||||
|
||||
@blueprint.route('/get_aws_db_instances/',
|
||||
methods=['GET'], endpoint='get_aws_db_instances')
|
||||
@login_required
|
||||
def get_db_instances():
|
||||
"""
|
||||
Fetch AWS DB Instances based on engine version.
|
||||
"""
|
||||
# Get Engine Version
|
||||
eng_version = request.args.get('eng_version')
|
||||
status, versions = get_aws_db_instances(eng_version)
|
||||
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=versions
|
||||
)
|
||||
|
||||
return make_json_response(data=versions)
|
||||
|
||||
|
||||
@blueprint.route('/get_aws_db_versions/',
|
||||
methods=['GET', 'POST'], endpoint='get_aws_db_versions')
|
||||
@login_required
|
||||
def get_db_versions():
|
||||
"""GET AWS Database Versions for AWS."""
|
||||
status, versions = get_aws_db_versions()
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=str(versions)
|
||||
)
|
||||
return make_json_response(data=versions)
|
||||
|
||||
|
||||
@blueprint.route('/get_aws_regions/',
|
||||
methods=['GET', 'POST'], endpoint='get_aws_regions')
|
||||
@login_required
|
||||
def get_db_versions():
|
||||
"""GET AWS Regions for AWS."""
|
||||
status, regions = get_aws_regions()
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=str(regions)
|
||||
)
|
||||
return make_json_response(data=regions)
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/deploy', methods=['POST'], endpoint='deploy_on_cloud'
|
||||
)
|
||||
@login_required
|
||||
def deploy_on_cloud():
|
||||
"""Deploy on Cloud"""
|
||||
|
||||
data = json.loads(request.data, encoding='utf-8')
|
||||
from subprocess import Popen, PIPE
|
||||
_cmd = 'python'
|
||||
_cmd_script = '{0}/pgacloud/pgacloud.py'.format(root)
|
||||
|
||||
args = [_cmd_script,
|
||||
'--debug',
|
||||
data['cloud'],
|
||||
'--region',
|
||||
str(data['secret']['aws_region']),
|
||||
'create-instance',
|
||||
'--name',
|
||||
data['instance_details']['aws_name'],
|
||||
'--db-name',
|
||||
data['db_details']['aws_db_name'],
|
||||
'--db-username',
|
||||
data['db_details']['aws_db_username'],
|
||||
'--db-port',
|
||||
str(data['db_details']['aws_db_port']),
|
||||
'--db-version',
|
||||
str(data['instance_details']['aws_db_version']),
|
||||
'--instance-type',
|
||||
data['instance_details']['aws_instance_type'],
|
||||
'--storage-type',
|
||||
data['instance_details']['aws_storage_type'],
|
||||
'--storage-size',
|
||||
str(data['instance_details']['aws_storage_size']),
|
||||
'--public-ip',
|
||||
str(data['instance_details']['aws_public_ip']),
|
||||
]
|
||||
|
||||
if data['instance_details']['aws_storage_type'] == 'io1':
|
||||
args.append('--storage-iops')
|
||||
args.append(str(data['instance_details']['aws_storage_IOPS']))
|
||||
|
||||
_cmd_msg = '{0} {1} {2}'.format(_cmd, _cmd_script, ' '.join(args))
|
||||
try:
|
||||
sid = _create_server({
|
||||
'gid': data['db_details']['gid'],
|
||||
'name': data['instance_details']['aws_name'],
|
||||
'db': data['db_details']['aws_db_name'],
|
||||
'username': data['db_details']['aws_db_username'],
|
||||
'port': data['db_details']['aws_db_port'],
|
||||
'cloud_status': -1
|
||||
})
|
||||
|
||||
p = BatchProcess(
|
||||
desc=CloudProcessDesc(sid, _cmd_msg, data['cloud'],
|
||||
data['instance_details']['aws_name']),
|
||||
cmd=_cmd,
|
||||
args=args
|
||||
)
|
||||
|
||||
env = dict()
|
||||
env['AWS_ACCESS_KEY_ID'] = data['secret']['aws_access_key']
|
||||
env['AWS_SECRET_ACCESS_KEY'] = data['secret']['aws_secret_access_key']
|
||||
|
||||
if 'aws_session_token' in data['secret'] and\
|
||||
data['secret']['aws_session_token'] is not None:
|
||||
env['AWS_SESSION_TOKEN'] = data['secret']['aws_session_token']
|
||||
|
||||
if 'aws_db_password' in data['db_details']:
|
||||
env['AWS_DATABASE_PASSWORD'] = data[
|
||||
'db_details']['aws_db_password']
|
||||
|
||||
p.set_env_variables(None, env=env)
|
||||
p.update_server_id(p.id, sid)
|
||||
p.start()
|
||||
|
||||
except Exception as e:
|
||||
current_app.logger.exception(e)
|
||||
return make_json_response(
|
||||
status=410,
|
||||
success=0,
|
||||
errormsg=str(e)
|
||||
)
|
||||
|
||||
# Return response
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={'job_id': 1, 'node': {
|
||||
'_id': sid,
|
||||
'_pid': data['db_details']['gid'],
|
||||
'connected': False,
|
||||
'_type': 'server',
|
||||
'icon': 'icon-server-cloud-deploy',
|
||||
'id': 'server_{}'.format(sid),
|
||||
'inode': True,
|
||||
'label': data['instance_details']['aws_name'],
|
||||
'server_type': 'pg',
|
||||
'module': 'pgadmin.node.server',
|
||||
'cloud_status': -1
|
||||
}}
|
||||
)
|
||||
|
||||
|
||||
def _create_server(data):
|
||||
"""Create Server"""
|
||||
server = Server(
|
||||
user_id=current_user.id,
|
||||
servergroup_id=data.get('gid'),
|
||||
name=data.get('name'),
|
||||
maintenance_db=data.get('db'),
|
||||
username=data.get('username'),
|
||||
ssl_mode='prefer',
|
||||
cloud_status=data.get('cloud_status')
|
||||
)
|
||||
|
||||
db.session.add(server)
|
||||
db.session.commit()
|
||||
|
||||
return server.id
|
||||
|
||||
|
||||
def update_server(data):
|
||||
"""Update Server."""
|
||||
server_data = data
|
||||
server = Server.query.filter_by(
|
||||
user_id=current_user.id,
|
||||
id=server_data['instance']['sid']
|
||||
).first()
|
||||
|
||||
if server is None:
|
||||
return False, "Could not find the server."
|
||||
|
||||
if server_data['instance'] == '' or\
|
||||
not server_data['instance']['status']:
|
||||
db.session.delete(server)
|
||||
else:
|
||||
server.host = server_data['instance']['Hostname']
|
||||
server.port = server_data['instance']['Port']
|
||||
server.cloud_status = 1
|
||||
|
||||
try:
|
||||
db.session.commit()
|
||||
except Exception as e:
|
||||
db.session.rollback()
|
||||
return False, e.message
|
||||
|
||||
_server = {
|
||||
'id': server.id,
|
||||
'servergroup_id': server.servergroup_id,
|
||||
'name': server.name,
|
||||
'cloud_status': server.cloud_status
|
||||
}
|
||||
if not server_data['instance']['status']:
|
||||
_server['status'] = False
|
||||
else:
|
||||
_server['status'] = True
|
||||
clear_aws_session()
|
||||
|
||||
return True, _server
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/update_cloud_process/<sid>', methods=['GET'],
|
||||
endpoint='update_cloud_process'
|
||||
)
|
||||
@login_required
|
||||
def update_cloud_process(sid):
|
||||
"""Update Cloud Server Process"""
|
||||
_process = Process.query.filter_by(user_id=current_user.id,
|
||||
server_id=sid).first()
|
||||
_process.acknowledge = None
|
||||
db.session.commit()
|
||||
return success_return()
|
||||
|
||||
|
||||
@blueprint.route(
|
||||
'/update_cloud_server', methods=['POST'],
|
||||
endpoint='update_cloud_server'
|
||||
)
|
||||
@login_required
|
||||
def update_cloud_server():
|
||||
"""Update Cloud Server."""
|
||||
server_data = json.loads(request.data, encoding='utf-8')
|
||||
status, server = update_server(server_data)
|
||||
|
||||
if not status:
|
||||
return make_json_response(
|
||||
status=410, success=0, errormsg=server
|
||||
)
|
||||
|
||||
return make_json_response(
|
||||
success=1,
|
||||
data={'node': {
|
||||
'sid': server.id,
|
||||
'gid': server.servergroup_id,
|
||||
'_type': 'server',
|
||||
'icon': 'icon-server-not-connected',
|
||||
'id': 'server_{}'.format(server.id),
|
||||
'label': server.name
|
||||
}}
|
||||
)
|
||||
|
||||
|
||||
class CloudProcessDesc(IProcessDesc):
|
||||
"""Cloud Server Process Description."""
|
||||
def __init__(self, _sid, _cmd, _provider, _instance_name):
|
||||
self.sid = _sid
|
||||
self.cmd = _cmd
|
||||
self.instance_name = _instance_name
|
||||
self.provider = 'Amazon RDS'
|
||||
|
||||
if _provider == 'rds':
|
||||
self.provider = 'Amazon RDS'
|
||||
elif _provider == 'azure':
|
||||
self.provider = 'Azure PostgreSQL'
|
||||
else:
|
||||
self.provider = 'EDB Big Animal'
|
||||
|
||||
@property
|
||||
def message(self):
|
||||
return "Deployment on {0} is started for instance {1}.".format(
|
||||
self.provider, self.instance_name)
|
||||
|
||||
def details(self, cmd, args):
|
||||
res = '<div>' + self.message
|
||||
res += '</div><div class="py-1">'
|
||||
res += '<div class="pg-bg-cmd enable-selection p-1">'
|
||||
res += html.safe_str(self.cmd)
|
||||
res += '</div></div>'
|
||||
|
||||
return res
|
||||
|
||||
@property
|
||||
def type_desc(self):
|
||||
return "Cloud Deployment"
|
||||
454
web/pgadmin/misc/cloud/static/js/CloudWizard.jsx
Normal file
454
web/pgadmin/misc/cloud/static/js/CloudWizard.jsx
Normal file
@@ -0,0 +1,454 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import url_for from 'sources/url_for';
|
||||
import React from 'react';
|
||||
import { Box, Table, TableBody, TableCell, TableHead, TableRow, Paper } from '@material-ui/core';
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import Wizard from '../../../../static/js/helpers/wizard/Wizard';
|
||||
import WizardStep from '../../../../static/js/helpers/wizard/WizardStep';
|
||||
import {FormFooterMessage, MESSAGE_TYPE, InputToggle } from '../../../../static/js/components/FormComponents';
|
||||
import getApiInstance from '../../../../static/js/api_instance';
|
||||
import SchemaView from '../../../../static/js/SchemaView';
|
||||
import Alertify from 'pgadmin.alertifyjs';
|
||||
import PropTypes from 'prop-types';
|
||||
import {CloudInstanceDetailsSchema, CloudDBCredSchema, DatabaseSchema} from './cloud_db_details_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
import pgAdmin from 'sources/pgadmin';
|
||||
import { getNodeAjaxOptions, getNodeListById } from 'pgbrowser/node_ajax';
|
||||
import { commonTableStyles } from '../../../../static/js/Theme';
|
||||
import clsx from 'clsx';
|
||||
|
||||
const useStyles = makeStyles(() =>
|
||||
({
|
||||
messageBox: {
|
||||
marginBottom: '1em',
|
||||
display: 'flex',
|
||||
},
|
||||
messagePadding: {
|
||||
flex: 2.5
|
||||
},
|
||||
toggleButton: {
|
||||
height: '100px',
|
||||
},
|
||||
table: {
|
||||
marginLeft: '4px',
|
||||
marginTop: '12px',
|
||||
},
|
||||
tableCellHeading: {
|
||||
fontWeight: 'bold',
|
||||
paddingLeft: '9px',
|
||||
},
|
||||
tableCell: {
|
||||
padding: '9px',
|
||||
paddingLeft: '11px',
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
export default function CloudWizard({ nodeInfo, nodeData }) {
|
||||
const classes = useStyles();
|
||||
const tableClasses = commonTableStyles();
|
||||
|
||||
var steps = ['Cloud Provider', 'Credentials', 'Instance Specification', 'Database Details', 'Review'];
|
||||
const [currentStep, setCurrentStep] = React.useState('');
|
||||
const [selectionVal, setCloudSelection] = React.useState('');
|
||||
const [errMsg, setErrMsg] = React.useState('');
|
||||
const [cloudInstanceDetailsInstance, setCloudInstanceDetailsInstance] = React.useState();
|
||||
const [cloudDBCredInstance, setCloudDBCredInstance] = React.useState();
|
||||
const [cloudDBInstance, setCloudDBInstance] = React.useState();
|
||||
const [cloudInstanceDetails, setCloudInstanceDetails] = React.useState({});
|
||||
const [cloudDBCred, setCloudDBCred] = React.useState({});
|
||||
const [cloudDBDetails, setCloudDBDetails] = React.useState({});
|
||||
const [callRDSAPI, setCallRDSAPI] = React.useState({});
|
||||
const axiosApi = getApiInstance();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (callRDSAPI == 2) {
|
||||
const cloudDBInstanceSchema = new CloudInstanceDetailsSchema({
|
||||
version: ()=>getNodeAjaxOptions('get_aws_db_versions', pgAdmin.Browser.Nodes['server'], nodeInfo, nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('cloud.get_aws_db_versions');
|
||||
}
|
||||
}),
|
||||
getInstances: (engine, reload, options) =>
|
||||
{
|
||||
return new Promise((resolve, reject)=>{
|
||||
const api = getApiInstance();
|
||||
var _url = url_for('cloud.get_aws_db_instances') ;
|
||||
|
||||
if (engine) _url += '?eng_version=' + engine;
|
||||
if (reload) {
|
||||
api.get(_url)
|
||||
.then(res=>{
|
||||
let data = res.data.data;
|
||||
resolve(data);
|
||||
})
|
||||
.catch((err)=>{
|
||||
reject(err);
|
||||
});
|
||||
} else {
|
||||
resolve(options);
|
||||
}
|
||||
});
|
||||
},
|
||||
instance_type: ()=>getNodeAjaxOptions('get_aws_db_instances', pgAdmin.Browser.Nodes['server'], nodeInfo, nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('cloud.get_aws_db_instances');
|
||||
}
|
||||
}),
|
||||
server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], nodeInfo, nodeData),
|
||||
}, {
|
||||
gid: nodeInfo['server_group']._id,
|
||||
});
|
||||
setCloudInstanceDetailsInstance(cloudDBInstanceSchema);
|
||||
}
|
||||
}, [callRDSAPI]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const cloudDBCredSchema = new CloudDBCredSchema({
|
||||
regions: ()=>getNodeAjaxOptions('get_aws_regions', pgAdmin.Browser.Nodes['server'], nodeInfo, nodeData, {
|
||||
useCache:false,
|
||||
cacheNode: 'server',
|
||||
customGenerateUrl: ()=>{
|
||||
return url_for('cloud.get_aws_regions');
|
||||
}
|
||||
}),
|
||||
});
|
||||
setCloudDBCredInstance(cloudDBCredSchema);
|
||||
|
||||
const cloudDBSchema = new DatabaseSchema({
|
||||
server_groups: ()=>getNodeListById(pgAdmin.Browser.Nodes['server_group'], nodeInfo, nodeData),
|
||||
},
|
||||
{
|
||||
gid: nodeInfo['server_group']._id,
|
||||
}
|
||||
);
|
||||
setCloudDBInstance(cloudDBSchema);
|
||||
|
||||
}, []);
|
||||
|
||||
const wizardStepChange = (data) => {
|
||||
setCurrentStep(data.currentStep);
|
||||
};
|
||||
|
||||
const validateCloudStep1 = (cloudDBCred) => {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudDBCred.aws_access_key) || isEmptyString(cloudDBCred.aws_secret_access_key)) {
|
||||
isError = true;
|
||||
}
|
||||
return isError;
|
||||
};
|
||||
|
||||
const validateCloudStep2 = (cloudInstanceDetails) => {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudInstanceDetails.aws_name) ||
|
||||
isEmptyString(cloudInstanceDetails.aws_db_version) || isEmptyString(cloudInstanceDetails.aws_instance_type) ||
|
||||
isEmptyString(cloudInstanceDetails.aws_storage_type)|| isEmptyString(cloudInstanceDetails.aws_storage_size)) {
|
||||
isError = true;
|
||||
}
|
||||
|
||||
if(cloudInstanceDetails.aws_storage_type == 'io1' && isEmptyString(cloudInstanceDetails.aws_storage_IOPS)) {
|
||||
isError = true;
|
||||
}
|
||||
if (isEmptyString(cloudInstanceDetails.aws_public_ip)) cloudInstanceDetails.aws_public_ip = '127.0.0.1/32';
|
||||
return isError;
|
||||
};
|
||||
|
||||
const validateCloudStep3 = (cloudDBDetails) => {
|
||||
let isError = false;
|
||||
if (isEmptyString(cloudDBDetails.aws_db_name) ||
|
||||
isEmptyString(cloudDBDetails.aws_db_username) || isEmptyString(cloudDBDetails.aws_db_password)) {
|
||||
isError = true;
|
||||
}
|
||||
if (isEmptyString(cloudDBDetails.aws_db_port)) cloudDBDetails.aws_db_port = 5432;
|
||||
if (isEmptyString(cloudDBDetails.gid)) cloudDBDetails.gid = nodeInfo['server_group']._id;
|
||||
return isError;
|
||||
};
|
||||
|
||||
const getStorageType = (cloudInstanceDetails) => {
|
||||
let _storage_type = 'General Purpose SSD (gp2)',
|
||||
_io1 = undefined;
|
||||
|
||||
if(cloudInstanceDetails.aws_storage_type == 'gp2') _storage_type = 'General Purpose SSD (gp2)';
|
||||
else if(cloudInstanceDetails.aws_storage_type == 'io1') {
|
||||
_storage_type = 'Provisioned IOPS SSD (io1)';
|
||||
_io1 = cloudInstanceDetails.aws_storage_IOPS;
|
||||
}
|
||||
else if(cloudInstanceDetails.aws_storage_type == 'magnetic') _storage_type = 'Magnetic';
|
||||
|
||||
return [_io1, _storage_type];
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
var _url = url_for('cloud.deploy_on_cloud');
|
||||
const post_data = {
|
||||
gid: nodeInfo.server_group._id,
|
||||
cloud: selectionVal,
|
||||
secret: cloudDBCred,
|
||||
instance_details:cloudInstanceDetails,
|
||||
db_details: cloudDBDetails
|
||||
};
|
||||
axiosApi.post(_url, post_data)
|
||||
.then((res) => {
|
||||
pgAdmin.Browser.Events.trigger('pgadmin:browser:tree:add', res.data.data.node, {'server_group': nodeInfo['server_group']});
|
||||
pgAdmin.Browser.Events.trigger('pgadmin-bgprocess:created', Alertify.cloudWizardDialog());
|
||||
Alertify.cloudWizardDialog().close();
|
||||
})
|
||||
.catch((error) => {
|
||||
Alertify.error(gettext(`Error while saving cloud wizard data: ${error.response.data.errormsg}`));
|
||||
});
|
||||
};
|
||||
|
||||
const disableNextCheck = () => {
|
||||
setCallRDSAPI(currentStep);
|
||||
let isError = false;
|
||||
switch (currentStep) {
|
||||
case 0:
|
||||
setCloudSelection('rds');
|
||||
break;
|
||||
case 1:
|
||||
isError = validateCloudStep1(cloudDBCred);
|
||||
break;
|
||||
case 2:
|
||||
isError = validateCloudStep2(cloudInstanceDetails);
|
||||
break;
|
||||
case 3:
|
||||
isError = validateCloudStep3(cloudDBDetails);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return isError;
|
||||
};
|
||||
|
||||
const onBeforeNext = (activeStep) => {
|
||||
return new Promise((resolve, reject)=>{
|
||||
if(activeStep == 1) {
|
||||
setErrMsg([MESSAGE_TYPE.INFO, 'Validating credentials...']);
|
||||
var _url = url_for('cloud.verify_credentials');
|
||||
const post_data = {
|
||||
cloud: selectionVal,
|
||||
secret: cloudDBCred,
|
||||
};
|
||||
axiosApi.post(_url, post_data)
|
||||
.then((res) => {
|
||||
if(!res.data.success) {
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, res.data.info]);
|
||||
reject();
|
||||
} else {
|
||||
setErrMsg(['', '']);
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
setErrMsg([MESSAGE_TYPE.ERROR, 'Error while checking cloud credentials']);
|
||||
reject();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onDialogHelp = () => {
|
||||
window.open(url_for('help.static', { 'filename': 'cloud_deployment.html' }), 'pgadmin_help');
|
||||
};
|
||||
|
||||
function createData(name, value) {
|
||||
return { name, value };
|
||||
}
|
||||
|
||||
let cloud = '';
|
||||
|
||||
switch (selectionVal) {
|
||||
case 'rds':
|
||||
cloud = 'Amazon RDS';
|
||||
break;
|
||||
case 'azure':
|
||||
cloud = 'Azure PostgreSQL';
|
||||
break;
|
||||
case 'biganimal':
|
||||
cloud = 'EDB Big Animal';
|
||||
break;
|
||||
}
|
||||
|
||||
const rows1 = [
|
||||
createData('Cloud', cloud),
|
||||
createData('Instance name', cloudInstanceDetails.aws_name),
|
||||
createData('Public IP', cloudInstanceDetails.aws_public_ip),
|
||||
];
|
||||
|
||||
const rows2 = [
|
||||
createData('PostgreSQL version', cloudInstanceDetails.aws_db_version),
|
||||
createData('Instance type', cloudInstanceDetails.aws_instance_type),
|
||||
];
|
||||
|
||||
let _storage_type = getStorageType(cloudInstanceDetails);
|
||||
|
||||
const rows3 = [
|
||||
createData('Storage type', _storage_type[1]),
|
||||
createData('Allocated storage', cloudInstanceDetails.aws_storage_size + ' GiB'),
|
||||
];
|
||||
if (_storage_type[0] !== undefined) {
|
||||
rows3.push(createData('Provisioned IOPS', _storage_type[0]));
|
||||
}
|
||||
|
||||
const rows4 = [
|
||||
createData('Database name', cloudDBDetails.aws_db_name),
|
||||
createData('Username', cloudDBDetails.aws_db_username),
|
||||
createData('Password', 'xxxxxxx'),
|
||||
createData('Port', cloudDBDetails.aws_db_port),
|
||||
];
|
||||
|
||||
const onErrClose = React.useCallback(()=>{
|
||||
setErrMsg([]);
|
||||
});
|
||||
|
||||
|
||||
const displayTableRows = (rows) => {
|
||||
return rows.map((row) => (
|
||||
<TableRow key={row.name} >
|
||||
<TableCell scope="row">{row.name}</TableCell>
|
||||
<TableCell align="right">{row.value}</TableCell>
|
||||
</TableRow>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Wizard
|
||||
title={gettext('Deploy Cloud Instance')}
|
||||
stepList={steps}
|
||||
disableNextStep={disableNextCheck}
|
||||
onStepChange={wizardStepChange}
|
||||
onSave={onSave}
|
||||
onHelp={onDialogHelp}
|
||||
beforeNext={onBeforeNext}>
|
||||
<WizardStep stepId={0}>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('Deploy on Amazon RDS cloud.')}</Box>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<InputToggle
|
||||
value='rds'
|
||||
options={[{'label': gettext('Amazon RDS'), value: 'rds'}]}
|
||||
className={classes.toggleButton}
|
||||
onChange={(value) => {
|
||||
setCloudSelection(value);}
|
||||
}
|
||||
>
|
||||
</InputToggle>
|
||||
</Box>
|
||||
<Box className={classes.messageBox}>
|
||||
<Box className={classes.messagePadding}>{gettext('More cloud providers are coming soon...')}</Box>
|
||||
</Box>
|
||||
</WizardStep>
|
||||
<WizardStep stepId={1} >
|
||||
{cloudDBCredInstance &&
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudDBCredInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
setCloudDBCred(changedData);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||
</WizardStep>
|
||||
<WizardStep stepId={2} >
|
||||
{cloudInstanceDetailsInstance &&
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudInstanceDetailsInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
setCloudInstanceDetails(changedData);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</WizardStep>
|
||||
<WizardStep stepId={3} >
|
||||
{cloudDBInstance &&
|
||||
<SchemaView
|
||||
formType={'dialog'}
|
||||
getInitData={() => { /*This is intentional (SonarQube)*/ }}
|
||||
viewHelperProps={{ mode: 'create' }}
|
||||
schema={cloudDBInstance}
|
||||
showFooter={false}
|
||||
isTabView={false}
|
||||
onDataChange={(isChanged, changedData) => {
|
||||
setCloudDBDetails(changedData);
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</WizardStep>
|
||||
<WizardStep stepId={4} >
|
||||
<Box className={classes.boxText}>{gettext('Please review the details before creating the cloud instance.')}</Box>
|
||||
<Paper variant="outlined" elevation={0} className={classes.summaryContainer}>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableBody>
|
||||
{displayTableRows(rows1)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{gettext('Version and Instance Details')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayTableRows(rows2)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{gettext('Storage Details')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayTableRows(rows3)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
<Table aria-label="simple table" className={clsx(tableClasses.table)}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell colSpan={2}>{gettext('Database Details')}</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{displayTableRows(rows4)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</Paper>
|
||||
</WizardStep>
|
||||
</Wizard>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
CloudWizard.propTypes = {
|
||||
nodeInfo: PropTypes.object,
|
||||
nodeData: PropTypes.object,
|
||||
};
|
||||
|
||||
|
||||
151
web/pgadmin/misc/cloud/static/js/cloud.js
Normal file
151
web/pgadmin/misc/cloud/static/js/cloud.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Theme from 'sources/Theme';
|
||||
import CloudWizard from './CloudWizard';
|
||||
|
||||
|
||||
// Cloud Wizard
|
||||
define('pgadmin.misc.cloud', [
|
||||
'sources/gettext', 'sources/url_for', 'jquery', 'underscore',
|
||||
'pgadmin.alertifyjs',
|
||||
'pgadmin.browser',
|
||||
'pgadmin.browser.wizard',
|
||||
], function(
|
||||
gettext, url_for, $, _, Alertify, pgBrowser
|
||||
) {
|
||||
|
||||
// if module is already initialized, refer to that.
|
||||
if (pgBrowser.Cloud) {
|
||||
return pgBrowser.Cloud;
|
||||
}
|
||||
|
||||
// Create an Object Cloud of pgBrowser class
|
||||
pgBrowser.Cloud = {
|
||||
init: function() {
|
||||
if (this.initialized)
|
||||
return;
|
||||
|
||||
this.initialized = true;
|
||||
|
||||
// Define the nodes on which the menus to be appear
|
||||
var menus = [{
|
||||
name: 'register_and_deploy_cloud_instance',
|
||||
module: this,
|
||||
applies: ['object', 'context'],
|
||||
callback: 'start_cloud_wizard',
|
||||
priority: 15,
|
||||
label: gettext('Deploy Cloud Instance...'),
|
||||
icon: 'wcTabIcon icon-server',
|
||||
enable: true,
|
||||
data: {action: 'create'},
|
||||
category: 'register',
|
||||
node: 'server_group',
|
||||
}, {
|
||||
name: 'register_and_deploy_cloud_instance',
|
||||
module: this,
|
||||
applies: ['object', 'context'],
|
||||
callback: 'start_cloud_wizard',
|
||||
priority: 15,
|
||||
label: gettext('Deploy Cloud Instance...'),
|
||||
icon: 'wcTabIcon icon-server',
|
||||
enable: true,
|
||||
data: {action: 'create'},
|
||||
category: 'register',
|
||||
node: 'server',
|
||||
}];
|
||||
|
||||
pgBrowser.add_menus(menus);
|
||||
return this;
|
||||
},
|
||||
|
||||
// Callback to draw Wizard Dialog
|
||||
start_cloud_wizard: function() {
|
||||
|
||||
// Declare Wizard dialog
|
||||
if (!Alertify.cloudWizardDialog) {
|
||||
Alertify.dialog('cloudWizardDialog', function factory() {
|
||||
|
||||
// Generate wizard main container
|
||||
var $container = $('<div class=\'wizard_dlg\' id=\'cloudWizardDlg\'></div>');
|
||||
return {
|
||||
main: function () {
|
||||
/*This is intentional (SonarQube)*/
|
||||
},
|
||||
setup: function () {
|
||||
return {
|
||||
// Set options for dialog
|
||||
options: {
|
||||
frameless: true,
|
||||
resizable: true,
|
||||
autoReset: false,
|
||||
maximizable: true,
|
||||
closable: true,
|
||||
closableByDimmer: false,
|
||||
modal: true,
|
||||
pinnable: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
build: function () {
|
||||
this.elements.content.appendChild($container.get(0));
|
||||
Alertify.pgDialogBuild.apply(this);
|
||||
var t = pgBrowser.tree,
|
||||
i = t.selected(),
|
||||
d = this.d = i ? t.itemData(i) : undefined,
|
||||
info = this.info = pgBrowser.tree.getTreeNodeHierarchy(i);
|
||||
|
||||
setTimeout(function () {
|
||||
if (document.getElementById('cloudWizardDlg')) {
|
||||
ReactDOM.render(
|
||||
<Theme>
|
||||
<CloudWizard nodeInfo={info} nodeData={d} />
|
||||
</Theme>,
|
||||
document.getElementById('cloudWizardDlg'));
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxHeight=0;
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxWidth='none';
|
||||
Alertify.cloudWizardDialog().elements.modal.style.overflow='visible';
|
||||
Alertify.cloudWizardDialog().elements.dimmer.style.display='none';
|
||||
}
|
||||
}, 500);
|
||||
|
||||
},
|
||||
prepare: function () {
|
||||
$container.empty().append('<div class=\'cloud_wizard_container\'></div>');
|
||||
},
|
||||
hooks: {
|
||||
// Triggered when the dialog is closed
|
||||
onclose: function () {
|
||||
// Clear the view and remove the react component.
|
||||
return setTimeout((function () {
|
||||
ReactDOM.unmountComponentAtNode(document.getElementById('cloudWizardDlg'));
|
||||
return Alertify.cloudWizardDialog().destroy();
|
||||
}), 500);
|
||||
},
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
// Call Grant Wizard Dialog and set dimensions for wizard
|
||||
Alertify.cloudWizardDialog('').set({
|
||||
onmaximize:function(){
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxHeight='initial';
|
||||
},
|
||||
onrestore:function(){
|
||||
Alertify.cloudWizardDialog().elements.modal.style.maxHeight=0;
|
||||
},
|
||||
}).resizeTo(920, 620);
|
||||
},
|
||||
|
||||
|
||||
};
|
||||
|
||||
return pgBrowser.Cloud;
|
||||
});
|
||||
292
web/pgadmin/misc/cloud/static/js/cloud_db_details_schema.ui.js
Normal file
292
web/pgadmin/misc/cloud/static/js/cloud_db_details_schema.ui.js
Normal file
@@ -0,0 +1,292 @@
|
||||
/////////////////////////////////////////////////////////////
|
||||
//
|
||||
// pgAdmin 4 - PostgreSQL Tools
|
||||
//
|
||||
// Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
// This software is released under the PostgreSQL Licence
|
||||
//
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
import gettext from 'sources/gettext';
|
||||
import BaseUISchema from 'sources/SchemaView/base_schema.ui';
|
||||
import { isEmptyString } from 'sources/validators';
|
||||
|
||||
|
||||
class CloudInstanceDetailsSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
aws_name: '',
|
||||
aws_public_ip: '127.0.0.1/32',
|
||||
...initValues
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
this.initValues = initValues;
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'aws_name', label: gettext('Instance name'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_public_ip', label: gettext('Public IP range'), type: 'text',
|
||||
mode: ['create'],
|
||||
helpMessage: gettext('IP Address range for permitting the inbound traffic. Ex: 127.0.0.1/32'),
|
||||
}, {
|
||||
type: 'nested-fieldset', label: gettext('Version & Instance'),
|
||||
mode: ['create'],
|
||||
schema: new InstanceSchema(this.fieldOptions.version,
|
||||
this.fieldOptions.instance_type,
|
||||
this.fieldOptions.getInstances),
|
||||
}, {
|
||||
type: 'nested-fieldset', label: gettext('Storage'),
|
||||
mode: ['create'],
|
||||
schema: new StorageSchema(),
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class CloudDBCredSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues = {}) {
|
||||
super({
|
||||
oid: null,
|
||||
aws_region: '',
|
||||
aws_access_key: '',
|
||||
aws_secret_access_key: '',
|
||||
aws_session_token: '',
|
||||
is_valid_cred: false,
|
||||
...initValues
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'aws_region', label: gettext('Region'),
|
||||
type: 'select',
|
||||
options: this.fieldOptions.regions,
|
||||
controlProps: { allowClear: false },
|
||||
noEmpty: true,
|
||||
helpMessage: gettext('The cloud instance will be deployed in the selected region.')
|
||||
},{
|
||||
id: 'aws_access_key', label: gettext('AWS access key'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_secret_access_key', label: gettext('AWS secret access key'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_session_token', label: gettext('AWS session token'), type: 'multiline',
|
||||
mode: ['create'], noEmpty: false,
|
||||
helpMessage: gettext('Temporary AWS session required session token.')
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class DatabaseSchema extends BaseUISchema {
|
||||
constructor(fieldOptions = {}, initValues={}) {
|
||||
super({
|
||||
oid: undefined,
|
||||
gid: undefined,
|
||||
aws_db_name: '',
|
||||
aws_db_username: '',
|
||||
aws_db_password: '',
|
||||
aws_db_confirm_password: '',
|
||||
aws_db_port: 5432,
|
||||
...initValues,
|
||||
});
|
||||
|
||||
this.fieldOptions = {
|
||||
...fieldOptions,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
validate(data, setErrMsg) {
|
||||
if(!isEmptyString(data.aws_db_password) && !isEmptyString(data.aws_db_confirm_password)
|
||||
&& data.aws_db_password != data.aws_db_confirm_password) {
|
||||
setErrMsg('aws_db_confirm_password', gettext('Passwords do not match.'));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
get idAttribute() {
|
||||
return 'oid';
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'gid', label: gettext('Server group'), type: 'select',
|
||||
options: this.fieldOptions.server_groups,
|
||||
mode: ['create'],
|
||||
controlProps: { allowClear: false },
|
||||
noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_name', label: gettext('Database name'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_username', label: gettext('Username'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_password', label: gettext('Password'), type: 'password',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_confirm_password', label: gettext('Confirm password'),
|
||||
type: 'password',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}, {
|
||||
id: 'aws_db_port', label: gettext('Port'), type: 'text',
|
||||
mode: ['create'], noEmpty: true,
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
export class InstanceSchema extends BaseUISchema {
|
||||
constructor(versionOpts, instanceOpts, getInstances) {
|
||||
super({
|
||||
aws_db_version: '',
|
||||
aws_db_instance_class: 'm',
|
||||
aws_instance_type: '',
|
||||
reload_instances: true,
|
||||
});
|
||||
this.versionOpts = versionOpts;
|
||||
this.instanceOpts = instanceOpts;
|
||||
this.getInstances = getInstances;
|
||||
this.instanceData = [];
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [{
|
||||
id: 'aws_db_version', label: gettext('Database version'),
|
||||
type: 'select',
|
||||
options: this.versionOpts,
|
||||
controlProps: { allowClear: false },
|
||||
deps: ['aws_name'],
|
||||
noEmpty: true,
|
||||
},{
|
||||
id: 'aws_db_instance_class', label: gettext('Instance class'),
|
||||
type: 'toggle',
|
||||
options: [
|
||||
{'label': gettext('Standard classes (includes m classes)'), value: 'm'},
|
||||
{'label': gettext('Memory optimized classes (includes r & x classes)'), value: 'x'},
|
||||
{'label': gettext('Burstable classes (includes t classes)'), value: 't'},
|
||||
], noEmpty: true, orientation: 'vertical',
|
||||
},{
|
||||
id: 'aws_instance_type', label: gettext('Instance type'),
|
||||
options: this.instanceOpts, noEmpty: true,
|
||||
controlProps: { allowClear: false },
|
||||
deps: ['aws_db_version', 'aws_db_instance_class'],
|
||||
depChange: (state, source)=> {
|
||||
if (source[0] == 'aws_db_instance_class') {
|
||||
state.reload_instances = false;
|
||||
} else {
|
||||
state.reload_instances = true;
|
||||
}
|
||||
},
|
||||
type: (state) => {
|
||||
return {
|
||||
type: 'select',
|
||||
options: ()=>this.getInstances(state.aws_db_version,
|
||||
state.reload_instances, state.instanceData),
|
||||
optionsLoaded: (options) => { state.instanceData = options; },
|
||||
optionsReloadBasis: state.aws_db_version + (state.aws_db_instance_class || 'm'),
|
||||
controlProps: {
|
||||
allowClear: false,
|
||||
filter: (options) => {
|
||||
let pattern = 'db.m';
|
||||
let pattern_1 = 'db.m';
|
||||
|
||||
if (state.aws_db_instance_class) {
|
||||
pattern = 'db.' + state.aws_db_instance_class;
|
||||
pattern_1 = 'db.' + state.aws_db_instance_class;
|
||||
}
|
||||
if (state.aws_db_instance_class == 'x') {
|
||||
pattern_1 = 'db.' + 'r';
|
||||
}
|
||||
return options.filter((option) => {
|
||||
return (option.value.includes(pattern) || option.value.includes(pattern_1));
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
},
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class StorageSchema extends BaseUISchema {
|
||||
constructor() {
|
||||
super({
|
||||
aws_storage_type: 'io1',
|
||||
aws_storage_size: 100,
|
||||
aws_storage_IOPS: 3000,
|
||||
aws_storage_msg: 'Minimum: 20 GiB. Maximum: 16,384 GiB.'
|
||||
});
|
||||
}
|
||||
|
||||
get baseFields() {
|
||||
return [
|
||||
{
|
||||
id: 'aws_storage_type', label: gettext('Storage type'), type: 'select',
|
||||
mode: ['create'],
|
||||
options: [
|
||||
{'label': gettext('General Purpose SSD (gp2)'), 'value': 'gp2'},
|
||||
{'label': gettext('Provisioned IOPS SSD (io1)'), 'value': 'io1'},
|
||||
{'label': gettext('Magnetic'), 'value': 'standard'}
|
||||
], noEmpty: true,
|
||||
},{
|
||||
id: 'aws_storage_size', label: gettext('Allocated storage'), type: 'text',
|
||||
mode: ['create'], noEmpty: true, deps: ['aws_storage_type'],
|
||||
depChange: (state)=> {
|
||||
if(state.aws_storage_type === 'io1') {
|
||||
state.aws_storage_size = 100;
|
||||
} else if(state.aws_storage_type === 'gp2') {
|
||||
state.aws_storage_size = 20;
|
||||
} else {
|
||||
state.aws_storage_size = 5;
|
||||
}
|
||||
},
|
||||
helpMessage: gettext('Size in GiB.')
|
||||
}, {
|
||||
id: 'aws_storage_IOPS', label: gettext('Provisioned IOPS'), type: 'text',
|
||||
mode: ['create'],
|
||||
visible: (state) => {
|
||||
if(state.aws_storage_type === 'io1') {
|
||||
state.aws_storage_IOPS = 3000;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} , deps: ['aws_storage_type']
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
CloudInstanceDetailsSchema,
|
||||
CloudDBCredSchema,
|
||||
DatabaseSchema,
|
||||
};
|
||||
171
web/pgadmin/misc/cloud/utils/rds.py
Normal file
171
web/pgadmin/misc/cloud/utils/rds.py
Normal file
@@ -0,0 +1,171 @@
|
||||
##########################################################################
|
||||
#
|
||||
# pgAdmin 4 - PostgreSQL Tools
|
||||
#
|
||||
# Copyright (C) 2013 - 2022, The pgAdmin Development Team
|
||||
# This software is released under the PostgreSQL Licence
|
||||
#
|
||||
##########################################################################
|
||||
|
||||
# AWS RDS PostgreSQL provider
|
||||
|
||||
import boto3
|
||||
import pickle
|
||||
from flask import session
|
||||
from boto3.session import Session
|
||||
|
||||
|
||||
class RDS():
|
||||
def __init__(self, access_key, secret_key, session_token=None,
|
||||
default_region='ap-south-1'):
|
||||
self._clients = {}
|
||||
|
||||
self._access_key = access_key
|
||||
self._secret_key = secret_key
|
||||
self._session_token = session_token
|
||||
|
||||
self._default_region = default_region
|
||||
|
||||
##########################################################################
|
||||
# AWS Helper functions
|
||||
##########################################################################
|
||||
def _get_aws_client(self, type):
|
||||
""" Create/cache/return an AWS client object """
|
||||
if type in self._clients:
|
||||
return self._clients[type]
|
||||
|
||||
session = boto3.Session(
|
||||
aws_access_key_id=self._access_key,
|
||||
aws_secret_access_key=self._secret_key,
|
||||
aws_session_token=self._session_token
|
||||
)
|
||||
|
||||
self._clients[type] = session.client(
|
||||
type, region_name=self._default_region)
|
||||
|
||||
return self._clients[type]
|
||||
|
||||
def get_available_db_version(self, engine='postgres'):
|
||||
rds = self._get_aws_client('rds')
|
||||
return rds.describe_db_engine_versions(Engine=engine)
|
||||
|
||||
def get_available_db_instance_class(self, engine='postgres',
|
||||
engine_version='9.6'):
|
||||
rds = self._get_aws_client('rds')
|
||||
_instances = rds.describe_orderable_db_instance_options(
|
||||
Engine=engine,
|
||||
EngineVersion=engine_version)
|
||||
_instances_list = _instances['OrderableDBInstanceOptions']
|
||||
_marker = _instances['Marker'] if 'Marker' in _instances else None
|
||||
while _marker:
|
||||
_tmp_instances = rds.describe_orderable_db_instance_options(
|
||||
Engine=engine,
|
||||
EngineVersion=engine_version,
|
||||
Marker=_marker)
|
||||
_instances_list = [*_instances_list,
|
||||
*_tmp_instances['OrderableDBInstanceOptions']]
|
||||
_marker = _tmp_instances['Marker'] if 'Marker'\
|
||||
in _tmp_instances else None
|
||||
|
||||
return _instances_list
|
||||
|
||||
def get_db_instance(self, instance_name):
|
||||
rds = self._get_aws_client('rds')
|
||||
return rds.describe_db_instances(
|
||||
DBInstanceIdentifier=instance_name)
|
||||
|
||||
def validate_credentials(self):
|
||||
client = self._get_aws_client('sts')
|
||||
try:
|
||||
identity = client.get_caller_identity()
|
||||
return True, identity
|
||||
except Exception as e:
|
||||
return False, str(e)
|
||||
finally:
|
||||
self._clients.pop('sts')
|
||||
|
||||
|
||||
def verify_aws_credentials(data):
|
||||
"""Verify Credentials"""
|
||||
session_token = data['secret']['aws_session_token'] if\
|
||||
'aws_session_token' in data['secret'] else None
|
||||
|
||||
if 'aws' not in session:
|
||||
session['aws'] = {}
|
||||
|
||||
if 'aws_rds_obj' not in session['aws'] or\
|
||||
session['aws']['secret'] != data['secret']:
|
||||
_rds = RDS(
|
||||
access_key=data['secret']['aws_access_key'],
|
||||
secret_key=data['secret']['aws_secret_access_key'],
|
||||
session_token=session_token,
|
||||
default_region=data['secret']['aws_region'])
|
||||
status, identity = _rds.validate_credentials()
|
||||
if status:
|
||||
session['aws']['secret'] = data['secret']
|
||||
session['aws']['aws_rds_obj'] = pickle.dumps(_rds, -1)
|
||||
return status, identity
|
||||
|
||||
return True, None
|
||||
|
||||
|
||||
def clear_aws_session():
|
||||
"""Clear AWS Session"""
|
||||
if 'aws' in session:
|
||||
session.pop('aws')
|
||||
|
||||
|
||||
def get_aws_db_instances(eng_version):
|
||||
"""Get AWS DB Instances"""
|
||||
if 'aws' not in session:
|
||||
return False, 'Session has not created yet.'
|
||||
|
||||
if not eng_version or eng_version == '' or eng_version == 'undefined':
|
||||
eng_version = '9.6.1'
|
||||
|
||||
rds_obj = pickle.loads(session['aws']['aws_rds_obj'])
|
||||
res = rds_obj.get_available_db_instance_class(
|
||||
engine_version=eng_version)
|
||||
versions_set = set()
|
||||
versions = []
|
||||
for value in res:
|
||||
versions_set.add(value['DBInstanceClass'])
|
||||
|
||||
for value in versions_set:
|
||||
versions.append({
|
||||
'label': value,
|
||||
'value': value
|
||||
})
|
||||
return True, versions
|
||||
|
||||
|
||||
def get_aws_db_versions():
|
||||
"""Get AWS DB Versions"""
|
||||
|
||||
if 'aws' not in session:
|
||||
return False, 'Session has not created yet.'
|
||||
|
||||
rds_obj = pickle.loads(session['aws']['aws_rds_obj'])
|
||||
db_versions = rds_obj.get_available_db_version()
|
||||
res = db_versions['DBEngineVersions']
|
||||
versions = []
|
||||
for value in res:
|
||||
versions.append({
|
||||
'label': value['DBEngineVersionDescription'],
|
||||
'value': value['EngineVersion']
|
||||
})
|
||||
return True, versions
|
||||
|
||||
|
||||
def get_aws_regions():
|
||||
"""Get AWS DB Versions"""
|
||||
clear_aws_session()
|
||||
_session = Session()
|
||||
res = _session.get_available_regions('rds')
|
||||
regions = []
|
||||
for value in res:
|
||||
regions.append({
|
||||
'label': value,
|
||||
'value': value
|
||||
})
|
||||
return True, regions
|
||||
Reference in New Issue
Block a user