diff --git a/web/pgacloud/providers/biganimal.py b/web/pgacloud/providers/biganimal.py index dc075f17c..5755eb3e4 100644 --- a/web/pgacloud/providers/biganimal.py +++ b/web/pgacloud/providers/biganimal.py @@ -19,7 +19,7 @@ from utils.io import debug, error, output class BigAnimalProvider(AbsProvider): - BASE_URL = 'https://portal.biganimal.com/api/v2' + BASE_URL = 'https://portal.biganimal.com/api/v3' def __init__(self): self._clients = {} @@ -47,6 +47,12 @@ class BigAnimalProvider(AbsProvider): parser_create_instance = parsers.add_parser('create-instance', help='create a new ' 'instance') + parser_create_instance.add_argument('--project', + required=True, + help='Project') + parser_create_instance.add_argument('--cloud-provider', + required=True, + help='Provider') parser_create_instance.add_argument('--region', required=True, help='name of the region') parser_create_instance.add_argument('--name', required=True, @@ -85,9 +91,6 @@ class BigAnimalProvider(AbsProvider): parser_create_instance.add_argument('--replicas', required=True, help='No. of Stand By Replicas') - parser_create_instance.add_argument('--cloud-provider', - required=True, - help='Provider') def cmd_create_instance(self, args): """ Create a biganimal cluster """ @@ -105,7 +108,8 @@ class BigAnimalProvider(AbsProvider): debug('Creating BigAnimal cluster: {}...'.format(args.name)) - _url = "{0}/{1}".format(self.BASE_URL, 'clusters') + _url = "{0}/projects/{1}/clusters".format(self.BASE_URL, + args.project) _headers = {"content-type": "application/json", "accept": "application/json", 'authorization': 'Bearer {0}'.format(self._access_key)} @@ -143,7 +147,7 @@ class BigAnimalProvider(AbsProvider): if cluster_resp.status_code == 202 and cluster_resp.content: cluster_info = json.loads(cluster_resp.content) instance_id = cluster_info['data']['clusterId'] - instance = self.get_instance_status(instance_id) + instance = self.get_instance_status(args.project, instance_id) data = {'instance': { 'ImageName': instance['clusterName'], 'Database Type': instance['pgType']['pgTypeName'], @@ -163,13 +167,15 @@ class BigAnimalProvider(AbsProvider): except Exception as e: debug(str(e)) - def get_instance_status(self, instance_id): + def get_instance_status(self, project_id, instance_id): """ Get the biganimal cluster status """ running = True status = None while running: - _url = "{0}/{1}/{2}".format(self.BASE_URL, 'clusters', instance_id) + _url = "{0}/projects/{1}/clusters/{2}".format(self.BASE_URL, + project_id, + instance_id) _headers = {"accept": "application/json", 'authorization': 'Bearer {0}'.format(self._access_key)} diff --git a/web/pgadmin/misc/cloud/biganimal/__init__.py b/web/pgadmin/misc/cloud/biganimal/__init__.py index 039cbb510..7a26c177f 100644 --- a/web/pgadmin/misc/cloud/biganimal/__init__.py +++ b/web/pgadmin/misc/cloud/biganimal/__init__.py @@ -50,7 +50,8 @@ class BigAnimalModule(PgAdminModule): 'biganimal.instance_types', 'biganimal.volume_types', 'biganimal.volume_properties', - 'biganimal.providers'] + 'biganimal.providers', + 'biganimal.projects'] blueprint = BigAnimalModule(MODULE_NAME, __name__, @@ -64,7 +65,8 @@ 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) + if status: + session['biganimal']['provider_obj'] = pickle.dumps(biganimal_obj, -1) return make_json_response(success=status, errormsg=error) @@ -82,25 +84,36 @@ def verification(): return make_json_response(data=verification_uri) -@blueprint.route('/regions/', - methods=['GET'], endpoint='regions') +@blueprint.route('/projects/', + methods=['GET'], endpoint='projects') @login_required -def biganimal_regions(provider_id): - """Get Regions.""" - biganimal_obj = pickle.loads(session['biganimal']['provider_obj']) - status, regions = biganimal_obj.get_regions(provider_id) - session['biganimal']['provider_obj'] = pickle.dumps(biganimal_obj, -1) - return make_json_response(data=regions) - - -@blueprint.route('/providers/', - methods=['GET'], endpoint='providers') -@login_required -def biganimal_providers(): +def biganimal_projects(): """Get Providers.""" biganimal_obj = pickle.loads(session['biganimal']['provider_obj']) - status, providers = biganimal_obj.get_providers() - return make_json_response(data=providers) + projects, error = biganimal_obj.get_projects() + return make_json_response(data=projects, errormsg=error) + + +@blueprint.route('/providers/', + methods=['GET'], endpoint='providers') +@login_required +def biganimal_providers(project_id): + """Get Providers.""" + biganimal_obj = pickle.loads(session['biganimal']['provider_obj']) + providers, error = biganimal_obj.get_providers(project_id) + session['biganimal']['provider_obj'] = pickle.dumps(biganimal_obj, -1) + return make_json_response(data=providers, errormsg=error) + + +@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() + session['biganimal']['provider_obj'] = pickle.dumps(biganimal_obj, -1) + return make_json_response(data=regions) @blueprint.route('/db_types/', @@ -165,7 +178,7 @@ def biganimal_volume_properties(region_id, provider_id, volume_type): class BigAnimalProvider(): """BigAnimal provider class""" - BASE_URL = 'https://portal.biganimal.com/api/v2' + BASE_URL = 'https://portal.biganimal.com/api/v3' def __init__(self): self.provider = {} @@ -177,6 +190,7 @@ class BigAnimalProvider(): self.token_status = -1 self.regions = [] self.get_auth_provider() + self.project_id = None def _get_headers(self): return { @@ -281,34 +295,33 @@ class BigAnimalProvider(): return True return False - def get_providers(self): + def get_providers(self, project_id): """Get cloud providers""" - _url = '{0}/cloud-providers'.format( - self.BASE_URL) + if not project_id: + return False, gettext('Project not provided.') + _url = '{0}/projects/{1}/cloud-providers'.format( + self.BASE_URL, project_id) providers = [] resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: + self.project_id = project_id provider_resp = json.loads(resp.content) for value in provider_resp['data']: providers.append({ 'label': value['cloudProviderName'], 'value': value['cloudProviderId'], - 'connected': value['connected'] - }) - return True, providers + 'connected': value['connected']}) + return providers, None elif resp.content: provider_resp = json.loads(resp.content) - return False, provider_resp['error']['message'] + return [], provider_resp['error']['message'] else: - return False, gettext('Error retrieving providers.') + return [], gettext('Error retrieving providers.') - def get_regions(self, provider_id): + def get_regions(self): """Get regions""" - if not provider_id: - return False, gettext('Provider not provided.') - _url = '{0}/cloud-providers/{1}/regions'.format( - self.BASE_URL, - provider_id) + _url = '{0}/projects/{1}/regions'.format( + self.BASE_URL, self.project_id) regions = [] resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: @@ -328,9 +341,8 @@ class BigAnimalProvider(): def get_postgres_types(self): """Get Postgres Types.""" - _url = "{0}/{1}".format( - self.BASE_URL, - 'pg-types') + _url = "{0}/projects/{1}/pg-types".format( + self.BASE_URL, self.project_id) pg_types = [] resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: @@ -349,10 +361,9 @@ class BigAnimalProvider(): if not cluster_type or not pg_type: return [] - _url = "{0}/pg-versions?clusterArchitectureIds={1}" \ - "&pgTypeIds={2}".format(self.BASE_URL, - cluster_type, - pg_type) + _url = "{0}/projects/{1}/pg-versions?clusterArchitectureIds={2}" \ + "&pgTypeIds={3}".format(self.BASE_URL, self.project_id, + cluster_type, pg_type) pg_versions = [] resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: @@ -368,15 +379,14 @@ class BigAnimalProvider(): """GEt Instance Types.""" if region_id not in self.regions or not provider_id: return [] - _url = '{0}/cloud-providers/{1}/regions/{2}/instance-types?' \ - 'sort=instanceTypeName'.format(self.BASE_URL, - provider_id, - region_id) + _url = '{0}/projects/{1}/cloud-providers/{2}/regions/{3}/' \ + 'instance-types?sort=instanceTypeName'.\ + format(self.BASE_URL, self.project_id, provider_id, region_id) resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: pg_types = json.loads(resp.content) - _sorted_data = sorted(pg_types['data'], key=lambda x: int(x['cpu']) - ) + _sorted_data = sorted(pg_types['data'], + key=lambda x: int(x['cpu'])) return _sorted_data return [] @@ -385,10 +395,8 @@ class BigAnimalProvider(): if region_id not in self.regions: return [] - _url = '{0}/cloud-providers/{1}/regions/{2}/volume-types'.format( - self.BASE_URL, - provider_id, - region_id) + _url = '{0}/projects/{1}/cloud-providers/{2}/regions/{3}/volume-types'\ + .format(self.BASE_URL, self.project_id, provider_id, region_id) volume_types = [] resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: @@ -399,8 +407,7 @@ class BigAnimalProvider(): 'label': value['volumeTypeName'], 'value': value['volumeTypeId'], 'supportedInstanceFamilyNames': value[ - 'supportedInstanceFamilyNames'] - }) + 'supportedInstanceFamilyNames']}) return volume_types def get_volume_properties(self, region_id, provider_id, volume_type): @@ -408,11 +415,10 @@ class BigAnimalProvider(): if region_id not in self.regions: return [] - _url = '{0}/cloud-providers/{1}/regions/{2}/volume-types/{3}/' \ - 'volume-properties'.format(self.BASE_URL, - provider_id, - region_id, - volume_type) + _url = '{0}/projects/{1}/cloud-providers/{2}/regions/{3}/' \ + 'volume-types/{4}/volume-properties'\ + .format(self.BASE_URL, self.project_id, provider_id, region_id, + volume_type) volume_properties = [] resp = requests.get(_url, headers=self._get_headers()) if resp.status_code == 200 and resp.content: @@ -424,6 +430,24 @@ class BigAnimalProvider(): }) return volume_properties + def get_projects(self): + projects = [] + _url = '{0}/projects'.format(self.BASE_URL) + resp = requests.get(_url, headers=self._get_headers()) + if resp.status_code == 200 and resp.content: + project_resp = json.loads(resp.content) + for value in project_resp['data']: + projects.append({ + 'label': value['projectName'], + 'value': value['projectId'] + }) + return projects, None + elif resp.content: + project_resp = json.loads(resp.content) + return [], project_resp['error']['message'] + else: + return [], gettext('Error retrieving projects.') + def clear_biganimal_session(): """Clear session data.""" @@ -451,6 +475,10 @@ def deploy_on_biganimal(data): 'create-instance', '--name', data['instance_details']['name'], + '--project', + str(data['cluster_details']['project']), + '--cloud-provider', + str(data['cluster_details']['provider']), '--region', str(data['instance_details']['region']), '--db-type', @@ -476,10 +504,7 @@ def deploy_on_biganimal(data): '--nodes', str(nodes), '--replicas', - str(data['cluster_details']['replicas']), - '--cloud-provider', - str(data['cluster_details']['provider']), - ] + str(data['cluster_details']['replicas'])] if 'biganimal_public_ip' in data['instance_details']: args.append('--public-ip') diff --git a/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx b/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx index 95921ea43..00236c628 100644 --- a/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx +++ b/web/pgadmin/misc/cloud/static/js/CloudWizard.jsx @@ -22,7 +22,7 @@ import pgAdmin from 'sources/pgadmin'; import {ToggleButtons, FinalSummary} from './cloud_components'; import { PrimaryButton } from '../../../../static/js/components/Buttons'; import {AwsCredentials, AwsInstanceDetails, AwsDatabaseDetails, validateCloudStep1, validateCloudStep2, validateCloudStep3} from './aws'; -import {BigAnimalInstance, BigAnimalDatabase, BigAnimalClusterType, getProviderOptions, validateBigAnimal, validateBigAnimalStep2, validateBigAnimalStep3, validateBigAnimalStep4} from './biganimal'; +import {BigAnimalInstance, BigAnimalDatabase, BigAnimalClusterType, validateBigAnimal, validateBigAnimalStep2, validateBigAnimalStep3, validateBigAnimalStep4} from './biganimal'; import { isEmptyString } from 'sources/validators'; import { AWSIcon, BigAnimalIcon, AzureIcon, GoogleCloudIcon } from '../../../../static/js/components/ExternalIcon'; import {AzureCredentials, AzureInstanceDetails, AzureDatabaseDetails, checkClusternameAvailbility, validateAzureStep2, validateAzureStep3} from './azure'; @@ -84,7 +84,6 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel}) const [bigAnimalInstanceData, setBigAnimalInstanceData] = React.useState({}); const [bigAnimalDatabaseData, setBigAnimalDatabaseData] = React.useState({}); const [bigAnimalClusterTypeData, setBigAnimalClusterTypeData] = React.useState({}); - const [bigAnimalProviders, setBigAnimalProviders] = React.useState({}); const [azureCredData, setAzureCredData] = React.useState({}); const [azureInstanceData, setAzureInstanceData] = React.useState({}); @@ -320,16 +319,6 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel}) setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]); reject(); }); - } else if (activeStep == 1 && cloudProvider == CLOUD_PROVIDERS.BIGANIMAL ) { - getProviderOptions() - .then((res)=>{ - setBigAnimalProviders(res); - setErrMsg(['', '']); - resolve(); - }).catch((error)=>{ - setErrMsg([MESSAGE_TYPE.ERROR, gettext(error)]); - reject(); - }); } else if (cloudProvider == CLOUD_PROVIDERS.AZURE) { if (activeStep == 1) { // Skip the current step @@ -477,7 +466,6 @@ export default function CloudWizard({ nodeInfo, nodeData, onClose, cloudPanel}) cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} - bigAnimalProviders={bigAnimalProviders} setBigAnimalClusterTypeData={setBigAnimalClusterTypeData} hostIP={hostIP} /> } diff --git a/web/pgadmin/misc/cloud/static/js/biganimal.js b/web/pgadmin/misc/cloud/static/js/biganimal.js index b4f5d9dd7..1e88f8001 100644 --- a/web/pgadmin/misc/cloud/static/js/biganimal.js +++ b/web/pgadmin/misc/cloud/static/js/biganimal.js @@ -17,67 +17,33 @@ import getApiInstance from '../../../../static/js/api_instance'; import { isEmptyString } from 'sources/validators'; import PropTypes from 'prop-types'; import gettext from 'sources/gettext'; -import { makeStyles } from '@material-ui/core/styles'; -import { AWSIcon, MSAzureIcon } from '../../../../static/js/components/ExternalIcon'; - - -const useStyles = makeStyles(() => - ({ - providerHeight: { - height: '5em', - }, - AwsIcon: { - width: '6rem', - } - }), -); const axiosApi = getApiInstance(); -export function getProviderOptions() { - return new Promise((resolve, reject) => { - axiosApi.get(url_for('biganimal.providers')) - .then((res) => { - if (res.data.data) { - let _options= [], - _options_label = {'azure': , - 'aws': }; - _.forEach(res.data.data, (val) => { - _options.push({ - 'label': _options_label[val['value']], - 'value': val['value'], - 'disabled': !val['connected'] - }); - }); - resolve(_options); - } - }) - .catch((error) => { - reject(gettext(`Error while getting the biganimal providers: ${error.response.data.errormsg}`)); - }); - }); -} - // BigAnimal Cluster Type export function BigAnimalClusterType(props) { const [bigAnimalClusterType, setBigAnimalClusterType] = React.useState(); - const classes = useStyles(); React.useMemo(() => { const bigAnimalClusterTypeSchema = new BigAnimalClusterTypeSchema({ - providers: ()=>getNodeAjaxOptions('biganimal_providers', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, { + projects: ()=>getNodeAjaxOptions('biganimal_projects', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, { useCache:false, cacheNode: 'server', customGenerateUrl: ()=>{ - return url_for('biganimal.providers'); + return url_for('biganimal.projects'); + } + }), + providers: (project)=>getNodeAjaxOptions('biganimal_providers', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, { + useCache:false, + cacheNode: 'server', + customGenerateUrl: ()=>{ + return url_for('biganimal.providers', {'project_id':project}); } }), }, { nodeInfo: props.nodeInfo, nodeData: props.nodeData, hostIP: props.hostIP, - classes: classes, - bigAnimalProviders: props.bigAnimalProviders, }); setBigAnimalClusterType(bigAnimalClusterTypeSchema); }, [props.cloudProvider]); @@ -99,8 +65,7 @@ BigAnimalClusterType.propTypes = { nodeData: PropTypes.object, cloudProvider: PropTypes.string, setBigAnimalClusterTypeData: PropTypes.func, - hostIP: PropTypes.string, - bigAnimalProviders: PropTypes.object, + hostIP: PropTypes.string }; @@ -110,11 +75,11 @@ export function BigAnimalInstance(props) { React.useMemo(() => { const bigAnimalSchema = new BigAnimalClusterSchema({ - regions: (provider_id)=>getNodeAjaxOptions('biganimal_regions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, { + regions: ()=>getNodeAjaxOptions('biganimal_regions', pgAdmin.Browser.Nodes['server'], props.nodeInfo, props.nodeData, { useCache:false, cacheNode: 'server', customGenerateUrl: ()=>{ - return url_for('biganimal.regions', {'provider_id': provider_id || 0}); + return url_for('biganimal.regions'); } }), instance_types: (region_id, provider_id)=>{ diff --git a/web/pgadmin/misc/cloud/static/js/biganimal_schema.ui.js b/web/pgadmin/misc/cloud/static/js/biganimal_schema.ui.js index 980ddff61..53f63ede9 100644 --- a/web/pgadmin/misc/cloud/static/js/biganimal_schema.ui.js +++ b/web/pgadmin/misc/cloud/static/js/biganimal_schema.ui.js @@ -17,6 +17,7 @@ class BigAnimalClusterTypeSchema extends BaseUISchema { constructor(fieldOptions = {}, initValues = {}) { super({ oid: undefined, + project: '', cluster_type: '', replicas: 0, provider: '', @@ -43,6 +44,18 @@ class BigAnimalClusterTypeSchema extends BaseUISchema { get baseFields() { return [ + { id: 'project', + label: gettext('Project'), + mode: ['create'], + noEmpty: true, + type: () => { + return { + type: 'select', + options: this.fieldOptions.projects + }; + }, + + }, { id: 'cluster_type', label: gettext('Cluster type'), noEmpty: true, type: () => { @@ -68,8 +81,17 @@ class BigAnimalClusterTypeSchema extends BaseUISchema { return state.cluster_type != 'ha'; } }, { id: 'provider', label: gettext('Cluster provider'), noEmpty: true, - type: 'toggle', className: this.initValues.classes.providerHeight, - options: this.initValues.bigAnimalProviders, + deps:['project'], + type: (state) => { + return { + type: 'select', + options: state.project + ? () => this.fieldOptions.providers(state.project) + : [], + optionsReloadBasis: state.project, + allowClear: false, + }; + } }, ]; } @@ -379,7 +401,7 @@ class BigAnimalDatabaseSchema extends BaseUISchema { type: (state) => { return { type: 'select', - options: ()=>this.fieldOptions.db_versions(this.initValues.cluster_typ, state.database_type), + options: ()=>this.fieldOptions.db_versions(this.initValues.cluster_type, state.database_type), optionsReloadBasis: state.database_type, }; }, @@ -439,8 +461,7 @@ class BigAnimalClusterSchema extends BaseUISchema { type: () => { return { type: 'select', - options: ()=>this.fieldOptions.regions(this.initValues.provider), - optionsReloadBasis: this.initValues.provider, + options: ()=>this.fieldOptions.regions() }; }, },{