mirror of
https://github.com/pgadmin-org/pgadmin4.git
synced 2025-02-25 18:55:31 -06:00
Added support for Azure PostgreSQL deployment in server mode. Fixes #7522
This commit is contained in:
parent
64e6700228
commit
59f5c0d955
@ -10,8 +10,6 @@ To deploy a PostgreSQL server on the Azure cloud, follow the below steps.
|
|||||||
:alt: Cloud Deployment
|
:alt: Cloud Deployment
|
||||||
:align: center
|
:align: center
|
||||||
|
|
||||||
**Note:** This feature is currently available in Desktop mode only.
|
|
||||||
|
|
||||||
Once you launch the tool, select the Azure PostgreSQL option.
|
Once you launch the tool, select the Azure PostgreSQL option.
|
||||||
Click on the *Next* button to proceed further.
|
Click on the *Next* button to proceed further.
|
||||||
|
|
||||||
|
@ -23,4 +23,5 @@ Bug fixes
|
|||||||
|
|
||||||
| `Issue #7517 <https://redmine.postgresql.org/issues/7517>`_ - Enable the start debugging button once execution is completed.
|
| `Issue #7517 <https://redmine.postgresql.org/issues/7517>`_ - Enable the start debugging button once execution is completed.
|
||||||
| `Issue #7518 <https://redmine.postgresql.org/issues/7518>`_ - Ensure that dashboard graph API is not called after the panel has been closed.
|
| `Issue #7518 <https://redmine.postgresql.org/issues/7518>`_ - Ensure that dashboard graph API is not called after the panel has been closed.
|
||||||
| `Issue #7523 <https://redmine.postgresql.org/issues/7517>`_ - Fixed typo error for Statistics on the table header.
|
| `Issue #7522 <https://redmine.postgresql.org/issues/7522>`_ - Added support for Azure PostgreSQL deployment in server mode.
|
||||||
|
| `Issue #7523 <https://redmine.postgresql.org/issues/7523>`_ - Fixed typo error for Statistics on the table header.
|
||||||
|
@ -8,12 +8,11 @@
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
""" Azure PostgreSQL provider """
|
""" Azure PostgreSQL provider """
|
||||||
|
|
||||||
from azure.mgmt.rdbms.postgresql_flexibleservers import \
|
from azure.mgmt.rdbms.postgresql_flexibleservers import \
|
||||||
PostgreSQLManagementClient
|
PostgreSQLManagementClient
|
||||||
from azure.mgmt.rdbms.postgresql_flexibleservers.models import Sku, SkuTier, \
|
from azure.mgmt.rdbms.postgresql_flexibleservers.models import Sku, SkuTier, \
|
||||||
CreateMode, Storage, Server, FirewallRule, HighAvailability
|
CreateMode, Storage, Server, FirewallRule, HighAvailability
|
||||||
from azure.identity import AzureCliCredential, InteractiveBrowserCredential, \
|
from azure.identity import AzureCliCredential, DeviceCodeCredential, \
|
||||||
AuthenticationRecord
|
AuthenticationRecord
|
||||||
from azure.mgmt.resource import ResourceManagementClient
|
from azure.mgmt.resource import ResourceManagementClient
|
||||||
from azure.core.exceptions import ResourceNotFoundError
|
from azure.core.exceptions import ResourceNotFoundError
|
||||||
@ -37,12 +36,13 @@ class AzureProvider(AbsProvider):
|
|||||||
self._client_secret = None
|
self._client_secret = None
|
||||||
self._subscription_id = None
|
self._subscription_id = None
|
||||||
self._default_region = None
|
self._default_region = None
|
||||||
self._use_interactive_browser_credential = False
|
self._interactive_browser_credential = False
|
||||||
self._available_capabilities = None
|
self._available_capabilities = None
|
||||||
self._credentials = None
|
self._credentials = None
|
||||||
self._authentication_record_json = None
|
self._authentication_record_json = None
|
||||||
self._cli_credentials = None
|
self._cli_credentials = None
|
||||||
self.azure_cred_cache_name = None
|
self._azure_cred_cache_name = None
|
||||||
|
self._azure_cred_cache_location = None
|
||||||
|
|
||||||
# Get the credentials
|
# Get the credentials
|
||||||
if 'AUTHENTICATION_RECORD_JSON' in os.environ:
|
if 'AUTHENTICATION_RECORD_JSON' in os.environ:
|
||||||
@ -56,7 +56,7 @@ class AzureProvider(AbsProvider):
|
|||||||
self._tenant_id = os.environ['AZURE_TENANT_ID']
|
self._tenant_id = os.environ['AZURE_TENANT_ID']
|
||||||
|
|
||||||
if 'AUTH_TYPE' in os.environ:
|
if 'AUTH_TYPE' in os.environ:
|
||||||
self._use_interactive_browser_credential = False \
|
self._interactive_browser_credential = False \
|
||||||
if os.environ['AUTH_TYPE'] == 'azure_cli_credential' else True
|
if os.environ['AUTH_TYPE'] == 'azure_cli_credential' else True
|
||||||
|
|
||||||
if 'AZURE_DATABASE_PASSWORD' in os.environ:
|
if 'AZURE_DATABASE_PASSWORD' in os.environ:
|
||||||
@ -150,49 +150,48 @@ class AzureProvider(AbsProvider):
|
|||||||
##########################################################################
|
##########################################################################
|
||||||
def _get_azure_credentials(self):
|
def _get_azure_credentials(self):
|
||||||
try:
|
try:
|
||||||
if self._use_interactive_browser_credential:
|
if self._interactive_browser_credential:
|
||||||
if self._authentication_record_json is None:
|
_credentials = self._azure_interactive_auth()
|
||||||
_credentials = self._azure_interactive_browser_credential()
|
|
||||||
_auth_record_ = _credentials.authenticate()
|
|
||||||
self._authentication_record_json = \
|
|
||||||
_auth_record_.serialize()
|
|
||||||
else:
|
|
||||||
deserialized_auth_record = AuthenticationRecord.\
|
|
||||||
deserialize(self._authentication_record_json)
|
|
||||||
_credentials = \
|
|
||||||
self._azure_interactive_browser_credential(
|
|
||||||
deserialized_auth_record)
|
|
||||||
else:
|
else:
|
||||||
if self._cli_credentials is None:
|
_credentials = self._azure_cli_auth()
|
||||||
self._cli_credentials = AzureCliCredential()
|
|
||||||
_credentials = self._cli_credentials
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
return True, _credentials
|
return True, _credentials
|
||||||
|
|
||||||
def _azure_interactive_browser_credential(
|
def _azure_cli_auth(self):
|
||||||
self, deserialized_auth_record=None):
|
if self._cli_credentials is None:
|
||||||
if deserialized_auth_record:
|
self._cli_credentials = AzureCliCredential()
|
||||||
_credential = InteractiveBrowserCredential(
|
return self._cli_credentials
|
||||||
|
|
||||||
|
def _azure_interactive_auth(self):
|
||||||
|
if self._authentication_record_json is None:
|
||||||
|
_interactive_credential = DeviceCodeCredential(
|
||||||
tenant_id=self._tenant_id,
|
tenant_id=self._tenant_id,
|
||||||
timeout=180,
|
timeout=180,
|
||||||
_cache=load_persistent_cache(
|
prompt_callback=None,
|
||||||
TokenCachePersistenceOptions(
|
_cache=load_persistent_cache(TokenCachePersistenceOptions(
|
||||||
name=self._azure_cred_cache_name,
|
name=self._azure_cred_cache_name,
|
||||||
allow_unencrypted_storage=True,
|
allow_unencrypted_storage=True,
|
||||||
cache_location=self._azure_cred_cache_location)),
|
cache_location=self._azure_cred_cache_location)
|
||||||
authentication_record=deserialized_auth_record)
|
)
|
||||||
else:
|
|
||||||
_credential = InteractiveBrowserCredential(
|
|
||||||
tenant_id=self._tenant_id,
|
|
||||||
timeout=180,
|
|
||||||
_cache=load_persistent_cache(
|
|
||||||
TokenCachePersistenceOptions(
|
|
||||||
name=self._azure_cred_cache_name,
|
|
||||||
allow_unencrypted_storage=True,
|
|
||||||
cache_location=self._azure_cred_cache_location))
|
|
||||||
)
|
)
|
||||||
return _credential
|
_auth_record = _interactive_credential.authenticate()
|
||||||
|
self._authentication_record_json = _auth_record.serialize()
|
||||||
|
else:
|
||||||
|
deserialized_auth_record = AuthenticationRecord.deserialize(
|
||||||
|
self._authentication_record_json)
|
||||||
|
_interactive_credential = DeviceCodeCredential(
|
||||||
|
tenant_id=self._tenant_id,
|
||||||
|
timeout=180,
|
||||||
|
prompt_callback=None,
|
||||||
|
_cache=load_persistent_cache(TokenCachePersistenceOptions(
|
||||||
|
name=self._azure_cred_cache_name,
|
||||||
|
allow_unencrypted_storage=True,
|
||||||
|
cache_location=self._azure_cred_cache_location)
|
||||||
|
),
|
||||||
|
authentication_record=deserialized_auth_record
|
||||||
|
)
|
||||||
|
return _interactive_credential
|
||||||
|
|
||||||
def _get_azure_client(self, type):
|
def _get_azure_client(self, type):
|
||||||
""" Create/cache/return an Azure client object """
|
""" Create/cache/return an Azure client object """
|
||||||
|
@ -507,6 +507,7 @@ class BatchProcess(object):
|
|||||||
err = 0
|
err = 0
|
||||||
cloud_server_id = 0
|
cloud_server_id = 0
|
||||||
cloud_instance = ''
|
cloud_instance = ''
|
||||||
|
pid = self.id
|
||||||
|
|
||||||
enc = sys.getdefaultencoding()
|
enc = sys.getdefaultencoding()
|
||||||
if enc == 'ascii':
|
if enc == 'ascii':
|
||||||
@ -519,7 +520,7 @@ class BatchProcess(object):
|
|||||||
self.stderr, stderr, err, ctime, _process.exit_code, enc
|
self.stderr, stderr, err, ctime, _process.exit_code, enc
|
||||||
)
|
)
|
||||||
|
|
||||||
from pgadmin.misc.cloud import update_server
|
from pgadmin.misc.cloud import update_server, clear_cloud_session
|
||||||
if out_completed and not _process.exit_code:
|
if out_completed and not _process.exit_code:
|
||||||
for value in stdout:
|
for value in stdout:
|
||||||
if 'instance' in value[1] and value[1] != '':
|
if 'instance' in value[1] and value[1] != '':
|
||||||
@ -530,12 +531,16 @@ class BatchProcess(object):
|
|||||||
'instance' in cloud_instance:
|
'instance' in cloud_instance:
|
||||||
cloud_instance['instance']['sid'] = cloud_server_id
|
cloud_instance['instance']['sid'] = cloud_server_id
|
||||||
cloud_instance['instance']['status'] = True
|
cloud_instance['instance']['status'] = True
|
||||||
|
cloud_instance['instance']['pid'] = pid
|
||||||
return update_server(cloud_instance)
|
return update_server(cloud_instance)
|
||||||
elif err_completed and _process.exit_code > 0:
|
elif err_completed and _process.exit_code > 0:
|
||||||
cloud_instance = {'instance': {}}
|
cloud_instance = {'instance': {}}
|
||||||
cloud_instance['instance']['sid'] = _process.server_id
|
cloud_instance['instance']['sid'] = _process.server_id
|
||||||
cloud_instance['instance']['status'] = False
|
cloud_instance['instance']['status'] = False
|
||||||
|
cloud_instance['instance']['pid'] = pid
|
||||||
return update_server(cloud_instance)
|
return update_server(cloud_instance)
|
||||||
|
else:
|
||||||
|
clear_cloud_session(pid)
|
||||||
return True, {}
|
return True, {}
|
||||||
|
|
||||||
def status(self, out=0, err=0):
|
def status(self, out=0, err=0):
|
||||||
|
@ -134,11 +134,7 @@ def deploy_on_cloud():
|
|||||||
elif data['cloud'] == 'biganimal':
|
elif data['cloud'] == 'biganimal':
|
||||||
status, resp = deploy_on_biganimal(data)
|
status, resp = deploy_on_biganimal(data)
|
||||||
elif data['cloud'] == 'azure':
|
elif data['cloud'] == 'azure':
|
||||||
if config.SERVER_MODE:
|
status, resp = deploy_on_azure(data)
|
||||||
status = False
|
|
||||||
resp = gettext('Invalid Operation for Server mode.')
|
|
||||||
else:
|
|
||||||
status, resp = deploy_on_azure(data)
|
|
||||||
else:
|
else:
|
||||||
status = False
|
status = False
|
||||||
resp = gettext('No cloud implementation.')
|
resp = gettext('No cloud implementation.')
|
||||||
@ -172,6 +168,7 @@ def deploy_on_cloud():
|
|||||||
def update_server(data):
|
def update_server(data):
|
||||||
"""Update Server."""
|
"""Update Server."""
|
||||||
server_data = data
|
server_data = data
|
||||||
|
pid = data['instance']['pid']
|
||||||
server = Server.query.filter_by(
|
server = Server.query.filter_by(
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
id=server_data['instance']['sid']
|
id=server_data['instance']['sid']
|
||||||
@ -204,16 +201,16 @@ def update_server(data):
|
|||||||
_server['status'] = False
|
_server['status'] = False
|
||||||
else:
|
else:
|
||||||
_server['status'] = True
|
_server['status'] = True
|
||||||
clear_cloud_session()
|
clear_cloud_session(pid)
|
||||||
|
|
||||||
return True, _server
|
return True, _server
|
||||||
|
|
||||||
|
|
||||||
def clear_cloud_session():
|
def clear_cloud_session(pid=None):
|
||||||
"""Clear cloud sessions."""
|
"""Clear cloud sessions."""
|
||||||
clear_aws_session()
|
clear_aws_session()
|
||||||
clear_biganimal_session()
|
clear_biganimal_session()
|
||||||
clear_azure_session()
|
clear_azure_session(pid)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route(
|
@blueprint.route(
|
||||||
|
@ -8,9 +8,8 @@
|
|||||||
# ##########################################################################
|
# ##########################################################################
|
||||||
|
|
||||||
# Azure implementation
|
# Azure implementation
|
||||||
import random
|
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
import random
|
||||||
from pgadmin.misc.cloud.utils import _create_server, CloudProcessDesc
|
from pgadmin.misc.cloud.utils import _create_server, CloudProcessDesc
|
||||||
from pgadmin.misc.bgprocess.processes import BatchProcess
|
from pgadmin.misc.bgprocess.processes import BatchProcess
|
||||||
from pgadmin import make_json_response
|
from pgadmin import make_json_response
|
||||||
@ -26,7 +25,7 @@ import os
|
|||||||
|
|
||||||
from azure.mgmt.rdbms.postgresql_flexibleservers import \
|
from azure.mgmt.rdbms.postgresql_flexibleservers import \
|
||||||
PostgreSQLManagementClient
|
PostgreSQLManagementClient
|
||||||
from azure.identity import AzureCliCredential, InteractiveBrowserCredential,\
|
from azure.identity import AzureCliCredential, DeviceCodeCredential,\
|
||||||
AuthenticationRecord
|
AuthenticationRecord
|
||||||
from azure.mgmt.resource import ResourceManagementClient
|
from azure.mgmt.resource import ResourceManagementClient
|
||||||
from azure.mgmt.subscription import SubscriptionClient
|
from azure.mgmt.subscription import SubscriptionClient
|
||||||
@ -57,7 +56,8 @@ class AzurePostgresqlModule(PgAdminModule):
|
|||||||
'azure.db_versions',
|
'azure.db_versions',
|
||||||
'azure.instance_types',
|
'azure.instance_types',
|
||||||
'azure.availability_zones',
|
'azure.availability_zones',
|
||||||
'azure.storage_types']
|
'azure.storage_types',
|
||||||
|
'azure.get_azure_verification_codes']
|
||||||
|
|
||||||
|
|
||||||
blueprint = AzurePostgresqlModule(MODULE_NAME, __name__,
|
blueprint = AzurePostgresqlModule(MODULE_NAME, __name__,
|
||||||
@ -101,6 +101,20 @@ def verify_credentials():
|
|||||||
return make_json_response(success=status, errormsg=error)
|
return make_json_response(success=status, errormsg=error)
|
||||||
|
|
||||||
|
|
||||||
|
@blueprint.route('/get_azure_verification_codes/',
|
||||||
|
methods=['GET'], endpoint='get_azure_verification_codes')
|
||||||
|
@login_required
|
||||||
|
def get_azure_verification_codes():
|
||||||
|
"""Get azure code for authentication."""
|
||||||
|
azure_auth_code = None
|
||||||
|
status = False
|
||||||
|
if 'azure' in session and 'azure_auth_code' in session['azure']:
|
||||||
|
azure_auth_code = session['azure']['azure_auth_code']
|
||||||
|
status = True
|
||||||
|
return make_json_response(success=status,
|
||||||
|
data=azure_auth_code)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/check_cluster_name_availability/',
|
@blueprint.route('/check_cluster_name_availability/',
|
||||||
methods=['GET'], endpoint='check_cluster_name_availability')
|
methods=['GET'], endpoint='check_cluster_name_availability')
|
||||||
@login_required
|
@login_required
|
||||||
@ -237,8 +251,7 @@ class Azure:
|
|||||||
self._clients = {}
|
self._clients = {}
|
||||||
self._tenant_id = tenant_id
|
self._tenant_id = tenant_id
|
||||||
self._session_token = session_token
|
self._session_token = session_token
|
||||||
self._use_interactive_browser_credential = \
|
self._use_interactive_credential = interactive_browser_credential
|
||||||
interactive_browser_credential
|
|
||||||
self.authentication_record_json = None
|
self.authentication_record_json = None
|
||||||
self._cli_credentials = None
|
self._cli_credentials = None
|
||||||
self._credentials = None
|
self._credentials = None
|
||||||
@ -246,72 +259,74 @@ class Azure:
|
|||||||
self.subscription_id = None
|
self.subscription_id = None
|
||||||
self._availability_zone = None
|
self._availability_zone = None
|
||||||
self._available_capabilities_list = []
|
self._available_capabilities_list = []
|
||||||
self.cache_name = current_user.username + "_msal.cache"
|
self.azure_cache_name = current_user.username \
|
||||||
|
+ str(random.randint(1, 9999)) + "_msal.cache"
|
||||||
self.azure_cache_location = config.AZURE_CREDENTIAL_CACHE_DIR + '/'
|
self.azure_cache_location = config.AZURE_CREDENTIAL_CACHE_DIR + '/'
|
||||||
|
|
||||||
##########################################################################
|
##########################################################################
|
||||||
# Azure Helper functions
|
# Azure Helper functions
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def validate_azure_credentials(self):
|
def validate_azure_credentials(self):
|
||||||
"""
|
"""
|
||||||
Validates azure credentials
|
Validates azure credentials
|
||||||
:return: True if valid credentials else false
|
:return: True if valid credentials else false
|
||||||
"""
|
"""
|
||||||
status, identity = self._get_azure_credentials()
|
status, identity = self._get_azure_credentials()
|
||||||
session['azure']['azure_cache_file_name'] = self.cache_name
|
session['azure']['azure_cache_file_name'] = self.azure_cache_name
|
||||||
error = ''
|
error = ''
|
||||||
if not status:
|
if not status:
|
||||||
error = identity
|
error = identity
|
||||||
return status, error
|
return status, error
|
||||||
|
|
||||||
def _get_azure_credentials(self):
|
def _get_azure_credentials(self):
|
||||||
"""
|
|
||||||
Gets azure credentials depending on
|
|
||||||
self._use_interactive_browser_credential
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
try:
|
try:
|
||||||
if self._use_interactive_browser_credential:
|
if self._use_interactive_credential:
|
||||||
if self.authentication_record_json is None:
|
_credentials = self._azure_interactive_auth()
|
||||||
_credentials = self._azure_interactive_browser_credential()
|
|
||||||
_auth_record_ = _credentials.authenticate()
|
|
||||||
self.authentication_record_json = _auth_record_.serialize()
|
|
||||||
else:
|
|
||||||
deserialized_auth_record = AuthenticationRecord. \
|
|
||||||
deserialize(self.authentication_record_json)
|
|
||||||
_credentials = \
|
|
||||||
self._azure_interactive_browser_credential(
|
|
||||||
deserialized_auth_record)
|
|
||||||
else:
|
else:
|
||||||
if self._cli_credentials is None:
|
_credentials = self._azure_cli_auth()
|
||||||
self._cli_credentials = AzureCliCredential()
|
|
||||||
self.list_subscriptions()
|
|
||||||
_credentials = self._cli_credentials
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return False, str(e)
|
return False, str(e)
|
||||||
return True, _credentials
|
return True, _credentials
|
||||||
|
|
||||||
def _azure_interactive_browser_credential(
|
def _azure_cli_auth(self):
|
||||||
self, deserialized_auth_record=None):
|
if self._cli_credentials is None:
|
||||||
if deserialized_auth_record:
|
self._cli_credentials = AzureCliCredential()
|
||||||
_credential = InteractiveBrowserCredential(
|
self.list_subscriptions()
|
||||||
tenant_id=self._tenant_id,
|
return self._cli_credentials
|
||||||
timeout=180,
|
|
||||||
_cache=load_persistent_cache(
|
@staticmethod
|
||||||
TokenCachePersistenceOptions(
|
def _azure_interactive_auth_prompt_callback(
|
||||||
name=self.cache_name,
|
verification_uri, user_code, expires_at):
|
||||||
allow_unencrypted_storage=True)),
|
azure_auth_code = {'verification_uri': verification_uri,
|
||||||
authentication_record=deserialized_auth_record)
|
'user_code': user_code,
|
||||||
else:
|
'expires_at': expires_at}
|
||||||
_credential = InteractiveBrowserCredential(
|
session['azure']['azure_auth_code'] = azure_auth_code
|
||||||
|
|
||||||
|
def _azure_interactive_auth(self):
|
||||||
|
if self.authentication_record_json is None:
|
||||||
|
_interactive_credential = DeviceCodeCredential(
|
||||||
tenant_id=self._tenant_id,
|
tenant_id=self._tenant_id,
|
||||||
timeout=180,
|
timeout=180,
|
||||||
|
prompt_callback=self._azure_interactive_auth_prompt_callback,
|
||||||
_cache=load_persistent_cache(TokenCachePersistenceOptions(
|
_cache=load_persistent_cache(TokenCachePersistenceOptions(
|
||||||
name=self.cache_name,
|
name=self.azure_cache_name, allow_unencrypted_storage=True)
|
||||||
allow_unencrypted_storage=True))
|
)
|
||||||
)
|
)
|
||||||
return _credential
|
_auth_record = _interactive_credential.authenticate()
|
||||||
|
self.authentication_record_json = _auth_record.serialize()
|
||||||
|
else:
|
||||||
|
deserialized_auth_record = AuthenticationRecord.deserialize(
|
||||||
|
self.authentication_record_json)
|
||||||
|
_interactive_credential = DeviceCodeCredential(
|
||||||
|
tenant_id=self._tenant_id,
|
||||||
|
timeout=180,
|
||||||
|
prompt_callback=self._azure_interactive_auth_prompt_callback,
|
||||||
|
_cache=load_persistent_cache(TokenCachePersistenceOptions(
|
||||||
|
name=self.azure_cache_name, allow_unencrypted_storage=True)
|
||||||
|
),
|
||||||
|
authentication_record=deserialized_auth_record
|
||||||
|
)
|
||||||
|
return _interactive_credential
|
||||||
|
|
||||||
def _get_azure_client(self, type):
|
def _get_azure_client(self, type):
|
||||||
""" Create/cache/return an Azure client object """
|
""" Create/cache/return an Azure client object """
|
||||||
@ -684,7 +699,7 @@ def deploy_on_azure(data):
|
|||||||
azure = session['azure']['azure_obj']
|
azure = session['azure']['azure_obj']
|
||||||
env['AZURE_SUBSCRIPTION_ID'] = azure.subscription_id
|
env['AZURE_SUBSCRIPTION_ID'] = azure.subscription_id
|
||||||
env['AUTH_TYPE'] = data['secret']['auth_type']
|
env['AUTH_TYPE'] = data['secret']['auth_type']
|
||||||
env['AZURE_CRED_CACHE_NAME'] = azure.cache_name
|
env['AZURE_CRED_CACHE_NAME'] = azure.azure_cache_name
|
||||||
env['AZURE_CRED_CACHE_LOCATION'] = azure.azure_cache_location
|
env['AZURE_CRED_CACHE_LOCATION'] = azure.azure_cache_location
|
||||||
if azure.authentication_record_json is not None:
|
if azure.authentication_record_json is not None:
|
||||||
env['AUTHENTICATION_RECORD_JSON'] = \
|
env['AUTHENTICATION_RECORD_JSON'] = \
|
||||||
@ -698,6 +713,14 @@ def deploy_on_azure(data):
|
|||||||
p.set_env_variables(None, env=env)
|
p.set_env_variables(None, env=env)
|
||||||
p.update_server_id(p.id, sid)
|
p.update_server_id(p.id, sid)
|
||||||
p.start()
|
p.start()
|
||||||
|
|
||||||
|
# add pid: cache file dict in session['azure_cache_files_list']
|
||||||
|
if 'azure_cache_files_list' in session and \
|
||||||
|
session['azure_cache_files_list'] is not None:
|
||||||
|
session['azure_cache_files_list'][p.id] = azure.azure_cache_name
|
||||||
|
else:
|
||||||
|
session['azure_cache_files_list'] = {p.id: azure.azure_cache_name}
|
||||||
|
|
||||||
return True, {'label': _label, 'sid': sid}
|
return True, {'label': _label, 'sid': sid}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
current_app.logger.exception(e)
|
current_app.logger.exception(e)
|
||||||
@ -706,12 +729,30 @@ def deploy_on_azure(data):
|
|||||||
del session['azure']['azure_obj']
|
del session['azure']['azure_obj']
|
||||||
|
|
||||||
|
|
||||||
def clear_azure_session():
|
def clear_azure_session(pid=None):
|
||||||
"""Clear session data."""
|
"""Clear session data."""
|
||||||
|
cache_file_to_delete = None
|
||||||
|
if 'azure_cache_files_list' in session and \
|
||||||
|
pid in session['azure_cache_files_list']:
|
||||||
|
cache_file_to_delete = session['azure_cache_files_list'][pid]
|
||||||
|
delete_azure_cache(cache_file_to_delete)
|
||||||
|
del session['azure_cache_files_list'][pid]
|
||||||
|
|
||||||
if 'azure' in session:
|
if 'azure' in session:
|
||||||
file_name = session['azure']['azure_cache_file_name']
|
if cache_file_to_delete is None and \
|
||||||
file = config.AZURE_CREDENTIAL_CACHE_DIR + '/' + file_name
|
'azure_cache_file_name' in session['azure']:
|
||||||
# Delete cache file if exists
|
cache_file_to_delete = session['azure']['azure_cache_file_name']
|
||||||
if os.path.exists(file):
|
delete_azure_cache(cache_file_to_delete)
|
||||||
os.remove(file)
|
|
||||||
session.pop('azure')
|
session.pop('azure')
|
||||||
|
|
||||||
|
|
||||||
|
def delete_azure_cache(file_name):
|
||||||
|
"""
|
||||||
|
Delete specified file from azure cache directory
|
||||||
|
:param file_name:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
file = config.AZURE_CREDENTIAL_CACHE_DIR + '/' + file_name
|
||||||
|
# Delete cache file if exists
|
||||||
|
if os.path.exists(file):
|
||||||
|
os.remove(file)
|
||||||
|
@ -325,10 +325,9 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
|||||||
setErrMsg([]);
|
setErrMsg([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
let cloud_providers = [{label: 'Amazon RDS', value: 'rds', icon: <AWSIcon className={classes.icon} />}, {label: 'EDB BigAnimal', value: 'biganimal', icon: <BigAnimalIcon className={classes.icon} />}];
|
let cloud_providers = [{label: 'Amazon RDS', value: 'rds', icon: <AWSIcon className={classes.icon} />},
|
||||||
if (pgAdmin.server_mode == 'False'){
|
{label: 'EDB BigAnimal', value: 'biganimal', icon: <BigAnimalIcon className={classes.icon} />},
|
||||||
cloud_providers.push({'label': 'Azure PostgreSQL', value: 'azure', icon: <AzureIcon className={classes.icon} /> });
|
{'label': 'Azure PostgreSQL', value: 'azure', icon: <AzureIcon className={classes.icon} /> }];
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CloudWizardEventsContext.Provider value={eventBus.current}>
|
<CloudWizardEventsContext.Provider value={eventBus.current}>
|
||||||
@ -367,7 +366,7 @@ export default function CloudWizard({ nodeInfo, nodeData }) {
|
|||||||
</Box>}
|
</Box>}
|
||||||
</Box>
|
</Box>
|
||||||
{cloudProvider == 'rds' && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}
|
{cloudProvider == 'rds' && <AwsCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setCloudDBCred={setCloudDBCred}/>}
|
||||||
<Box>
|
<Box flexGrow={1}>
|
||||||
{cloudProvider == 'azure' && <AzureCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setAzureCredData={setAzureCredData}/>}
|
{cloudProvider == 'azure' && <AzureCredentials cloudProvider={cloudProvider} nodeInfo={nodeInfo} nodeData={nodeData} setAzureCredData={setAzureCredData}/>}
|
||||||
</Box>
|
</Box>
|
||||||
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
<FormFooterMessage type={errMsg[0]} message={errMsg[1]} onClose={onErrClose} />
|
||||||
|
@ -53,7 +53,28 @@ export function AzureCredentials(props) {
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.ERROR, gettext(`Error while verification Microsoft Azure: ${error.response.data.errormsg}`)]);
|
_eventBus.fireEvent('SET_ERROR_MESSAGE_FOR_CLOUD_WIZARD',[MESSAGE_TYPE.ERROR, gettext(`Error while verification Microsoft Azure: ${error.response.data.errormsg}`)]);
|
||||||
reject(false);
|
reject(false);
|
||||||
});});
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getAuthCode:()=>{
|
||||||
|
let _url_get_azure_verification_codes = url_for('azure.get_azure_verification_codes');
|
||||||
|
const axiosApi = getApiInstance();
|
||||||
|
return new Promise((resolve, reject)=>{
|
||||||
|
const interval = setInterval(()=>{
|
||||||
|
axiosApi.get(_url_get_azure_verification_codes)
|
||||||
|
.then((res)=>{
|
||||||
|
if (res.data.success){
|
||||||
|
clearInterval(interval);
|
||||||
|
window.open(res.data.data.verification_uri, 'azure_authentication');
|
||||||
|
resolve(res);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error)=>{
|
||||||
|
clearInterval(interval);
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
setCloudDBCredInstance(azureCloudDBCredSchema);
|
setCloudDBCredInstance(azureCloudDBCredSchema);
|
||||||
|
@ -21,6 +21,9 @@ class AzureCredSchema extends BaseUISchema {
|
|||||||
auth_type: 'interactive_browser_credential',
|
auth_type: 'interactive_browser_credential',
|
||||||
azure_tenant_id: '',
|
azure_tenant_id: '',
|
||||||
azure_subscription_id: '',
|
azure_subscription_id: '',
|
||||||
|
is_authenticating: false,
|
||||||
|
is_authenticated: false,
|
||||||
|
auth_code: '',
|
||||||
...initValues,
|
...initValues,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -97,30 +100,83 @@ class AzureCredSchema extends BaseUISchema {
|
|||||||
id: 'auth_btn',
|
id: 'auth_btn',
|
||||||
mode: ['create'],
|
mode: ['create'],
|
||||||
deps: ['auth_type', 'azure_tenant_id'],
|
deps: ['auth_type', 'azure_tenant_id'],
|
||||||
|
type: 'button',
|
||||||
btnName: gettext('Click here to authenticate yourself to Microsoft Azure'),
|
btnName: gettext('Click here to authenticate yourself to Microsoft Azure'),
|
||||||
type: (state) => {
|
|
||||||
return {
|
|
||||||
type: 'button',
|
|
||||||
onClick: () => {
|
|
||||||
obj.fieldOptions.authenticateAzure(state.auth_type, state.azure_tenant_id).then((res)=>{state._disabled_auth_btn= res;});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
},
|
|
||||||
helpMessage: gettext(
|
helpMessage: gettext(
|
||||||
'After clicking the button above you will be redirected to the Microsoft Azure authentication page in a new browser tab if the Interactive Browser option is selected.'
|
'After clicking the button above you will be redirected to the Microsoft Azure authentication page in a new browser tab if the Interactive Browser option is selected.'
|
||||||
),
|
),
|
||||||
depChange: (state, source)=> {
|
depChange: (state, source)=> {
|
||||||
if(source[0] == 'auth_type' || source[0] == 'azure_tenant_id'){
|
if(source == 'auth_type' || source == 'azure_tenant_id'){
|
||||||
state._disabled_auth_btn = false;
|
return {is_authenticated: false, auth_code: ''};
|
||||||
}
|
}
|
||||||
|
if(source == 'auth_btn') {
|
||||||
|
return {is_authenticating: true};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deferredDepChange: (state, source)=>{
|
||||||
|
return new Promise((resolve, reject)=>{
|
||||||
|
/* button clicked */
|
||||||
|
if(source == 'auth_btn') {
|
||||||
|
obj.fieldOptions.authenticateAzure(state.auth_type, state.azure_tenant_id)
|
||||||
|
.then(()=>{
|
||||||
|
resolve(()=>({
|
||||||
|
is_authenticated: true,
|
||||||
|
is_authenticating: false,
|
||||||
|
auth_code: ''
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
},
|
},
|
||||||
disabled: (state)=> {
|
disabled: (state)=> {
|
||||||
if(state.auth_type == 'interactive_browser_credential' && state.azure_tenant_id == ''){
|
if(state.auth_type == 'interactive_browser_credential' && state.azure_tenant_id == ''){
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return state._disabled_auth_btn;
|
return state.is_authenticating || state.is_authenticated;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'is_authenticating',
|
||||||
|
visible: false,
|
||||||
|
type: '',
|
||||||
|
deps:['auth_btn'],
|
||||||
|
deferredDepChange: (state, source)=>{
|
||||||
|
return new Promise((resolve, reject)=>{
|
||||||
|
if(source == 'auth_btn' && state.auth_type == 'interactive_browser_credential' && state.is_authenticating ) {
|
||||||
|
obj.fieldOptions.getAuthCode()
|
||||||
|
.then((res)=>{
|
||||||
|
resolve(()=>{
|
||||||
|
return {
|
||||||
|
is_authenticating: false,
|
||||||
|
auth_code: res.data.data.user_code,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err)=>{
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'auth_code',
|
||||||
|
mode: ['create'],
|
||||||
|
deps: ['auth_btn'],
|
||||||
|
type: (state)=>({
|
||||||
|
type: 'note',
|
||||||
|
text: `To complete the authenticatation, use a web browser to open the page https://microsoft.com/devicelogin and enter the code : <strong>${state.auth_code}</strong>`,
|
||||||
|
}),
|
||||||
|
visible: (state)=>{
|
||||||
|
return Boolean(state.auth_code);
|
||||||
|
},
|
||||||
|
controlProps: {
|
||||||
|
raw: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,10 +224,22 @@ export const MappedFormControl = (props) => {
|
|||||||
newProps.type = typeProps;
|
newProps.type = typeProps;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let origOnClick = newProps.onClick;
|
||||||
|
newProps.onClick = ()=>{
|
||||||
|
origOnClick?.();
|
||||||
|
/* Consider on click as change for button.
|
||||||
|
Just increase state val by 1 to inform the deps and self depChange */
|
||||||
|
newProps.onChange?.((newProps.state[props.id]||0)+1);
|
||||||
|
};
|
||||||
|
|
||||||
/* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
|
/* Filter out garbage props if any using ALLOWED_PROPS_FIELD */
|
||||||
return <MappedFormControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM))} />;
|
return <MappedFormControlBase {..._.pick(newProps, _.union(ALLOWED_PROPS_FIELD_COMMON, ALLOWED_PROPS_FIELD_FORM))} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
MappedFormControl.propTypes = {
|
||||||
|
id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired
|
||||||
|
};
|
||||||
|
|
||||||
export const MappedCellControl = (props) => {
|
export const MappedCellControl = (props) => {
|
||||||
let newProps = { ...props };
|
let newProps = { ...props };
|
||||||
let cellProps = evalFunc(null, newProps.cell, newProps.row);
|
let cellProps = evalFunc(null, newProps.cell, newProps.row);
|
||||||
|
@ -494,20 +494,22 @@ function SchemaDialogView({
|
|||||||
|
|
||||||
useEffect(()=>{
|
useEffect(()=>{
|
||||||
if(sessData.__deferred__?.length > 0) {
|
if(sessData.__deferred__?.length > 0) {
|
||||||
|
let items = sessData.__deferred__;
|
||||||
sessDispatch({
|
sessDispatch({
|
||||||
type: SCHEMA_STATE_ACTIONS.CLEAR_DEFERRED_QUEUE,
|
type: SCHEMA_STATE_ACTIONS.CLEAR_DEFERRED_QUEUE,
|
||||||
});
|
});
|
||||||
|
|
||||||
let item = sessData.__deferred__[0];
|
items.forEach((item)=>{
|
||||||
item.promise.then((resFunc)=>{
|
item.promise.then((resFunc)=>{
|
||||||
sessDispatch({
|
sessDispatch({
|
||||||
type: SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE,
|
type: SCHEMA_STATE_ACTIONS.DEFERRED_DEPCHANGE,
|
||||||
path: item.action.path,
|
path: item.action.path,
|
||||||
depChange: item.action.depChange,
|
depChange: item.action.depChange,
|
||||||
listener: {
|
listener: {
|
||||||
...item.listener,
|
...item.listener,
|
||||||
callback: resFunc,
|
callback: resFunc,
|
||||||
},
|
},
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1118,12 +1118,13 @@ PlainString.propTypes = {
|
|||||||
value: PropTypes.any,
|
value: PropTypes.any,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function FormNote({ text, className }) {
|
export function FormNote({ text, className, controlProps }) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
/* If raw, then remove the styles and icon */
|
||||||
return (
|
return (
|
||||||
<Box className={className}>
|
<Box className={className}>
|
||||||
<Paper elevation={0} className={classes.noteRoot}>
|
<Paper elevation={0} className={controlProps?.raw ? '' : classes.noteRoot}>
|
||||||
<Box paddingRight="0.25rem"><DescriptionIcon fontSize="small" /></Box>
|
{!controlProps?.raw && <Box paddingRight="0.25rem"><DescriptionIcon fontSize="small" /></Box>}
|
||||||
<Box>{HTMLReactParse(text || '')}</Box>
|
<Box>{HTMLReactParse(text || '')}</Box>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
@ -1132,6 +1133,7 @@ export function FormNote({ text, className }) {
|
|||||||
FormNote.propTypes = {
|
FormNote.propTypes = {
|
||||||
text: PropTypes.string,
|
text: PropTypes.string,
|
||||||
className: CustomPropTypes.className,
|
className: CustomPropTypes.className,
|
||||||
|
controlProps: PropTypes.object,
|
||||||
};
|
};
|
||||||
|
|
||||||
const useStylesFormFooter = makeStyles((theme) => ({
|
const useStylesFormFooter = makeStyles((theme) => ({
|
||||||
|
Loading…
Reference in New Issue
Block a user