Skip to content

Commit

Permalink
[rdbms-up] new extension for simplified rdbms flows. (Azure#475)
Browse files Browse the repository at this point in the history
* initial mysql extension

* renamed extension to rdbms-up and added server creation flow

* finished logic for getting ip addr and some smart defaults

* finished mysql up logic

* msg

* added some stuff

* default all params and run mysql commands

* added logic for connection strings and subsequent calls with same params

* ensure proper state between commands and defaults

* updated random words to also be lower than server requirement for length

* check for dynamic ip addresses and create rules for all detected

* message edit

* static check

* flake8

* codeowner

* license

* always warn users about firewall rule
  • Loading branch information
williexu authored Jan 22, 2019
1 parent c8a3944 commit 1341f4b
Show file tree
Hide file tree
Showing 236 changed files with 18,299 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

/src/storage-preview/ @williexu

/src/rdbms-up/ @williexu

/src/dev-spaces-preview/ @saurabsa

/src/dms-preview/ @temandr
Expand Down
32 changes: 32 additions & 0 deletions src/rdbms-up/azext_rdbms_up/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core import AzCommandsLoader
# pylint: disable=unused-import
import azext_rdbms_up._help


class RdbmsUpCommandsLoader(AzCommandsLoader):
def __init__(self, cli_ctx=None):
from azure.cli.core.commands import CliCommandType
rdbms_up_custom = CliCommandType(
operations_tmpl='azext_rdbms_up.custom#{}')
super(RdbmsUpCommandsLoader, self).__init__(cli_ctx=cli_ctx,
custom_command_type=rdbms_up_custom,
min_profile="2017-03-10-profile")

def load_command_table(self, args):
super(RdbmsUpCommandsLoader, self).load_command_table(args)
from .commands import load_command_table
load_command_table(self, args)
return self.command_table

def load_arguments(self, command):
super(RdbmsUpCommandsLoader, self).load_arguments(command)
from ._params import load_arguments
load_arguments(self, command)


COMMAND_LOADER_CLS = RdbmsUpCommandsLoader
60 changes: 60 additions & 0 deletions src/rdbms-up/azext_rdbms_up/_client_factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.profiles import ResourceType

# CLIENT FACTORIES

RM_URI_OVERRIDE = 'AZURE_CLI_RDBMS_RM_URI'
SUB_ID_OVERRIDE = 'AZURE_CLI_RDBMS_SUB_ID'
CLIENT_ID = 'AZURE_CLIENT_ID'
TENANT_ID = 'AZURE_TENANT_ID'
CLIENT_SECRET = 'AZURE_CLIENT_SECRET'


def get_mysql_management_client(cli_ctx, **_):
from os import getenv
from azext_rdbms_up.vendored_sdks.azure_mgmt_rdbms.mysql import MySQLManagementClient

# Allow overriding resource manager URI using environment variable
# for testing purposes. Subscription id is also determined by environment
# variable.
rm_uri_override = getenv(RM_URI_OVERRIDE)
if rm_uri_override:
client_id = getenv(CLIENT_ID)
if client_id:
from azure.common.credentials import ServicePrincipalCredentials
credentials = ServicePrincipalCredentials(
client_id=client_id,
secret=getenv(CLIENT_SECRET),
tenant=getenv(TENANT_ID))
else:
from msrest.authentication import Authentication # pylint: disable=import-error
credentials = Authentication()

return MySQLManagementClient(
subscription_id=getenv(SUB_ID_OVERRIDE),
base_url=rm_uri_override,
credentials=credentials)
else:
# Normal production scenario.
return get_mgmt_service_client(cli_ctx, MySQLManagementClient)


def cf_mysql_servers(cli_ctx, _):
return get_mysql_management_client(cli_ctx).servers


def cf_mysql_firewall_rules(cli_ctx, _):
return get_mysql_management_client(cli_ctx).firewall_rules


def cf_mysql_config(cli_ctx, _):
return get_mysql_management_client(cli_ctx).configurations


def resource_client_factory(cli_ctx, **_):
return get_mgmt_service_client(cli_ctx, ResourceType.MGMT_RESOURCE_RESOURCES)
12 changes: 12 additions & 0 deletions src/rdbms-up/azext_rdbms_up/_help.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from knack.help_files import helps


helps['mysql up'] = """
type: command
short-summary: Experimental command to create an Azure Database for MySQL server and configure a firewall rule.
"""
35 changes: 35 additions & 0 deletions src/rdbms-up/azext_rdbms_up/_params.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core.commands.parameters import tags_type, get_location_type, get_enum_type
from azext_rdbms_up.vendored_sdks.azure_mgmt_rdbms.mysql.models.my_sql_management_client_enums import (
SslEnforcementEnum, GeoRedundantBackup
)


def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statements
with self.argument_context('mysql up') as c:
c.argument('sku_name', options_list=['--sku-name'], default='GP_Gen5_4',
help='The name of the sku, typically, tier + family + cores, e.g. B_Gen4_1, GP_Gen5_8.')
c.argument('backup_retention', type=int,
help='The number of days a backup is retained.')
c.argument('geo_redundant_backup', arg_type=get_enum_type(GeoRedundantBackup),
default=GeoRedundantBackup.disabled.value, help='Enable Geo-redundant or not for server backup.')
c.argument('storage_mb', options_list=['--storage-size'], type=int,
help='The max storage size of the server. Unit is megabytes.')
c.argument('location', arg_type=get_location_type(self.cli_ctx))
c.argument('version', help='Server version', default='5.7')
c.argument('server_name', options_list=['--server-name', '-s'], help='Name of the server.')
c.argument('administrator_login', options_list=['--admin-user', '-u'], arg_group='Authentication',
help='The login username of the administrator.')
c.argument('administrator_login_password', options_list=['--admin-password', '-p'], arg_group='Authentication',
help='The login password of the administrator.')
c.extra('generate_password', help='Generate a password.', arg_group='Authentication')
c.argument('ssl_enforcement', arg_type=get_enum_type(SslEnforcementEnum),
default=SslEnforcementEnum.disabled.value,
help='Enable ssl enforcement or not when connect to server.')
c.argument('database_name', options_list=['--database-name', '-d'],
help='The name of a database to initialize.')
c.argument('tags', tags_type)
58 changes: 58 additions & 0 deletions src/rdbms-up/azext_rdbms_up/_validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

import uuid
from six.moves import configparser
from azure.cli.core.commands.validators import get_default_location_from_resource_group
from azure.mgmt.resource.resources.models import ResourceGroup
from azext_rdbms_up._client_factory import resource_client_factory
from azext_rdbms_up.random_name.generate import generate_username
from azext_rdbms_up.util import create_random_resource_name, get_config_value, set_config_value
from knack.log import get_logger

logger = get_logger(__name__)

DEFAULT_LOCATION = 'westus2'
DEFAULT_DATABASE_NAME = 'sampledb'


def process_mysql_namespace(cmd, namespace):
# Create smart defaults
if _get_value(namespace, 'location', 'location') is None:
if namespace.resource_group_name is None:
namespace.location = 'westus2'
else:
get_default_location_from_resource_group(cmd, namespace)
_set_value(namespace, 'location', 'location', namespace.location)

if _get_value(namespace, 'resource_group_name', 'group') is None:
# create new resource group
namespace.resource_group_name = create_random_resource_name('group')
resource_client = resource_client_factory(cmd.cli_ctx)
params = ResourceGroup(location=namespace.location)
logger.warning('Creating Resource Group \'%s\' ...', namespace.resource_group_name)
resource_client.resource_groups.create_or_update(namespace.resource_group_name, params)
_set_value(namespace, 'resource_group_name', 'group', namespace.resource_group_name)

_set_value(namespace, 'server_name', 'server', create_random_resource_name('server'))
_set_value(namespace, 'administrator_login', 'login', generate_username())
if namespace.generate_password:
namespace.administrator_login_password = str(uuid.uuid4())
del namespace.generate_password
_set_value(namespace, 'database_name', 'database', DEFAULT_DATABASE_NAME)


def _set_value(namespace, attribute, option, default, cache=True):
if getattr(namespace, attribute) is None:
try:
setattr(namespace, attribute, get_config_value(option))
except (configparser.NoSectionError, configparser.NoOptionError):
setattr(namespace, attribute, default)
if cache:
set_config_value(option, getattr(namespace, attribute))


def _get_value(namespace, attribute, option):
return getattr(namespace, attribute, None) or get_config_value(option, None)
4 changes: 4 additions & 0 deletions src/rdbms-up/azext_rdbms_up/azext_metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"azext.minCliCoreVersion": "2.0.46",
"azext.isPreview": true
}
19 changes: 19 additions & 0 deletions src/rdbms-up/azext_rdbms_up/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# --------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.core.commands import CliCommandType
from azure.cli.command_modules.rdbms._client_factory import cf_mysql_servers
from azext_rdbms_up._validators import process_mysql_namespace


def load_command_table(self, _): # pylint: disable=too-many-locals, too-many-statements
mysql_servers_sdk = CliCommandType(
operations_tmpl='azext_rdbms_up.vendored_sdks.azure_mgmt_rdbms.mysql.operations.servers_operations'
'#ServersOperations.{}',
client_factory=cf_mysql_servers
)

with self.command_group('mysql', mysql_servers_sdk, client_factory=cf_mysql_servers) as g:
g.custom_command('up', 'mysql_up', validator=process_mysql_namespace)
Loading

0 comments on commit 1341f4b

Please sign in to comment.