Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RDBMS] Private DNS zone parameter added for restore command, high availability validator #18218

Merged
merged 10 commits into from
Jun 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
import string
import yaml
from knack.log import get_logger
from msrestazure.tools import parse_resource_id
from msrestazure.azure_exceptions import CloudError
from azure.cli.core.azclierror import AuthenticationError
from azure.core.paging import ItemPaged
from azure.cli.core.commands.client_factory import get_subscription_id
from azure.cli.core.commands import LongRunningOperation, _is_poller
from azure.cli.core.azclierror import RequiredArgumentMissingError, InvalidArgumentValueError
from azure.cli.command_modules.role.custom import create_service_principal_for_rbac
from azure.mgmt.resource.resources.models import ResourceGroup
from msrestazure.tools import parse_resource_id
from ._client_factory import resource_client_factory, cf_mysql_flexible_location_capabilities, cf_postgres_flexible_location_capabilities

logger = get_logger(__name__)
Expand Down Expand Up @@ -182,8 +184,11 @@ def get_mysql_list_skus_info(cmd, location):

def _parse_list_skus(result, database_engine):
result = _get_list_from_paged_response(result)
single_az = False
if not result:
raise InvalidArgumentValueError("No available SKUs in this location")
if len(result) == 1:
single_az = True

tiers = result[0].supported_flexible_server_editions
tiers_dict = {}
Expand Down Expand Up @@ -219,8 +224,8 @@ def _parse_list_skus(result, database_engine):
tiers_dict[tier_name] = tier_dict

if database_engine == 'mysql':
return tiers_dict, iops_dict
return tiers_dict
return tiers_dict, iops_dict, single_az
return tiers_dict, single_az


def _get_available_values(sku_info, argument, tier=None):
Expand Down Expand Up @@ -333,9 +338,13 @@ def run_subprocess_get_output(command):
return process


def register_credential_secrets(cmd, server, repository):
def register_credential_secrets(cmd, database_engine, server, repository):
logger.warning('Adding secret "AZURE_CREDENTIALS" to github repository')
resource_group = parse_resource_id(server.id)["resource_group"]
scope = "/subscriptions/{}/resourceGroups/{}".format(get_subscription_id(cmd.cli_ctx), resource_group)
provider = "DBforMySQL"
if database_engine == "postgresql":
provider = "DBforPostgreSQL"
scope = "/subscriptions/{}/resourceGroups/{}/providers/Microsoft.{}/flexibleServers/{}".format(get_subscription_id(cmd.cli_ctx), resource_group, provider, server.name)

app = create_service_principal_for_rbac(cmd, name=server.name, role='contributor', scopes=[scope])
app['clientId'], app['clientSecret'], app['tenantId'] = app.pop('appId'), app.pop('password'), app.pop('tenant')
Expand All @@ -356,13 +365,14 @@ def register_credential_secrets(cmd, server, repository):
os.remove(credential_file)


def register_connection_secrets(cmd, database_engine, server, database_name, administrator_login, administrator_login_password, repository):
def register_connection_secrets(cmd, database_engine, server, database_name, administrator_login, administrator_login_password, repository, connection_string_name):
logger.warning("Added secret %s to github repository", connection_string_name)
if database_engine == 'postgresql':
connection_string = "host={} port=5432 dbname={} user={} password={} sslmode=require".format(server.fully_qualified_domain_name, database_name, administrator_login, administrator_login_password)
run_subprocess('gh secret set {} --repo {} -b"{}"'.format(AZURE_POSTGRESQL_CONNECTION_STRING, repository, connection_string))
run_subprocess('gh secret set {} --repo {} -b"{}"'.format(connection_string_name, repository, connection_string))
elif database_engine == 'mysql':
connection_string = "Server={}; Port=3306; Database={}; Uid={}; Pwd={}; SslMode=Preferred;".format(server.fully_qualified_domain_name, database_name, administrator_login, administrator_login_password)
run_subprocess('gh secret set {} --repo {} -b"{}"'.format(AZURE_MYSQL_CONNECTION_STRING, repository, connection_string))
run_subprocess('gh secret set {} --repo {} -b"{}"'.format(connection_string_name, repository, connection_string))


def fill_action_template(cmd, database_engine, server, database_name, administrator_login, administrator_login_password, file_name, action_name, repository):
Expand All @@ -373,28 +383,40 @@ def fill_action_template(cmd, database_engine, server, database_name, administra

process = run_subprocess_get_output("gh secret list --repo {}".format(repository))
github_secrets = process.stdout.read().strip().decode('UTF-8')
connection_string = AZURE_POSTGRESQL_CONNECTION_STRING if database_engine == 'postgresql' else AZURE_MYSQL_CONNECTION_STRING
# connection_string = AZURE_POSTGRESQL_CONNECTION_STRING if database_engine == 'postgresql' else AZURE_MYSQL_CONNECTION_STRING

if AZURE_CREDENTIALS not in github_secrets:
register_credential_secrets(cmd,
server=server,
repository=repository)

if connection_string not in github_secrets:
try:
register_credential_secrets(cmd,
database_engine=database_engine,
server=server,
repository=repository)
except CloudError:
raise AuthenticationError('You do not have authorization to create a service principal to run azure service in github actions. \n'
'Please create a service principal that has access to the database server and add "AZURE_CREDENTIALS" secret to your github repository. \n'
'Follow the instruction here "aka.ms/github-actions-azure-credentials".')

connection_string_name = server.name.upper().replace("-", "_") + "_" + database_name.upper().replace("-", "_") + "_" + database_engine.upper() + "_CONNECTION_STRING"
if connection_string_name not in github_secrets:
register_connection_secrets(cmd,
database_engine=database_engine,
server=server,
database_name=database_name,
administrator_login=administrator_login,
administrator_login_password=administrator_login_password,
repository=repository)
repository=repository,
connection_string_name=connection_string_name)

current_location = os.path.dirname(__file__)

with open(current_location + "/templates/" + database_engine + "_githubaction_template.yaml", "r") as template_file:
template = yaml.safe_load(template_file)
template['jobs']['build']['steps'][2]['with']['server-name'] = server.fully_qualified_domain_name
template['jobs']['build']['steps'][2]['with']['sql-file'] = file_name
if database_engine == 'postgresql':
template['jobs']['build']['steps'][2]['with']['plsql-file'] = file_name
else:
template['jobs']['build']['steps'][2]['with']['sql-file'] = file_name
template['jobs']['build']['steps'][2]['with']['connection-string'] = "${{ secrets." + connection_string_name + " }}"
with open(action_dir + action_name + '.yml', 'w', encoding='utf8') as yml_file:
yml_file.write("on: [workflow_dispatch]\n")
yml_file.write(yaml.dump(template))
Expand Down
4 changes: 3 additions & 1 deletion src/azure-cli/azure/cli/command_modules/rdbms/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,7 @@ def _flexible_server_params(command_group):
c.argument('high_availability', default="Disabled", options_list=['--high-availability'], help='Enable or disable high availability feature. Default value is Disabled. High availability can only be set during flexible server create time')
c.argument('assign_identity', options_list=['--assign-identity'],
help='Generate and assign an Azure Active Directory Identity for this server for use with key management services like Azure KeyVault. No need to enter extra argument.')
c.argument('private_dns_zone_arguments', options_list=['--private-dns-zone'], help='The name or id of new or existing private dns zone. You can use the private dns zone from same resource group, different resource group, or different subscription. If you want to use a zone from different resource group or subscription, please provide resource Id. CLI creates a new private dns zone within the same resource group if not provided by users.')
c.argument('private_dns_zone_arguments', options_list=['--private-dns-zone'], help='This parameter only applies for a server with private access. The name or id of new or existing private dns zone. You can use the private dns zone from same resource group, different resource group, or different subscription. If you want to use a zone from different resource group or subscription, please provide resource Id. CLI creates a new private dns zone within the same resource group if not provided by users.')
c.argument('database_name', id_part=None, arg_type=database_name_setter_arg_type, options_list=['--database-name', '-d'], help='The name of the database to be created when provisioning the database server')

with self.argument_context('{} flexible-server delete'.format(command_group)) as c:
Expand All @@ -320,6 +320,8 @@ def _flexible_server_params(command_group):
help='The name of the source server to restore from.')
c.argument('zone', options_list=['--zone'],
help='Availability zone into which to provision the resource.')
c.argument('private_dns_zone_arguments', options_list=['--private-dns-zone'],
help='This parameter only applies for a server with private access. The name or id of new or existing private dns zone. You can use the private dns zone from same resource group, different resource group, or different subscription. If you want to use a zone from different resource group or subscription, please provide resource Id. CLI creates a new private dns zone within the same resource group if not provided by users.')
elif command_group == 'mysql':
c.argument('source_server', options_list=['--source-server'],
help='The name or resource ID of the source server to restore from.')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,15 +195,14 @@ def github_actions_setup(cmd, client, resource_group_name, server_name, database
if allow_push:
logger.warning("Pushing the created action file to origin %s branch", branch)
run_subprocess("git push origin {}".format(branch))
github_actions_run(action_name, branch)
else:
logger.warning('You did not set --allow-push parameter. Please push the prepared file %s to your remote repo and run "deploy run" command to activate the workflow.', action_path)


def github_actions_run(action_name, branch):

gitcli_check_and_login()
logger.warning("Created event for %s.yml in branch %s", action_name, branch)
logger.warning("Created an event for %s.yml in branch %s", action_name, branch)
run_subprocess("gh workflow run {}.yml --ref {}".format(action_name, branch))


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def flexible_server_create(cmd, client, resource_group_name=None, server_name=No
# validator
if location is None:
location = DEFAULT_LOCATION_MySQL
sku_info, iops_info = get_mysql_list_skus_info(cmd, location)
sku_info, iops_info, single_az = get_mysql_list_skus_info(cmd, location)
mysql_arguments_validator(tier, sku_name, storage_mb, backup_retention, sku_info, version=version)

db_context = DbContext(
Expand All @@ -49,6 +49,8 @@ def flexible_server_create(cmd, client, resource_group_name=None, server_name=No
if high_availability is not None and high_availability.lower() == 'enabled':
if tier == 'Burstable':
raise ArgumentUsageError("High availability is not supported for Burstable tier")
if single_az:
raise ArgumentUsageError("This region is single availability zone. High availability is not supported in a single availability zone region.")

# Raise error when user passes values for both parameters
if subnet_arm_resource_id is not None and public_access is not None:
Expand Down Expand Up @@ -173,13 +175,12 @@ def flexible_server_update_custom_func(cmd, instance,
tags=None,
auto_grow=None,
assign_identity=False,
ha_enabled=None,
DaeunYim marked this conversation as resolved.
Show resolved Hide resolved
replication_role=None,
maintenance_window=None,
iops=None):
# validator
location = ''.join(instance.location.lower().split())
sku_info, iops_info = get_mysql_list_skus_info(cmd, location)
sku_info, iops_info, _ = get_mysql_list_skus_info(cmd, location)
mysql_arguments_validator(tier, sku_name, storage_mb, backup_retention, sku_info, instance=instance)

server_module_path = instance.__module__
Expand Down Expand Up @@ -247,7 +248,6 @@ def flexible_server_update_custom_func(cmd, instance,
ssl_enforcement=ssl_enforcement,
delegated_subnet_arguments=instance.delegated_subnet_arguments,
tags=tags,
ha_enabled=ha_enabled,
replication_role=replication_role)

if assign_identity:
Expand Down Expand Up @@ -543,8 +543,7 @@ def _determine_iops(storage_gb, iops_info, iops_input, tier, sku_name):
def get_free_iops(storage_in_mb, iops_info, tier, sku_name):
free_iops = MINIMUM_IOPS + (storage_in_mb // 1024) * 3
max_supported_iops = iops_info[tier][sku_name] # free iops cannot exceed maximum supported iops for the sku
logger.warning(iops_info[tier])
logger.warning(iops_info[tier][sku_name])

return min(free_iops, max_supported_iops)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# region create without args
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
# pylint: disable=raise-missing-from
# pylint: disable=raise-missing-from, unbalanced-tuple-unpacking
def flexible_server_create(cmd, client,
resource_group_name=None, server_name=None,
location=None, backup_retention=None,
Expand All @@ -44,7 +44,7 @@ def flexible_server_create(cmd, client,
# validator
if location is None:
location = DEFAULT_LOCATION_PG
sku_info = get_postgres_list_skus_info(cmd, location)
sku_info, single_az = get_postgres_list_skus_info(cmd, location)
pg_arguments_validator(tier, sku_name, storage_mb, sku_info, version=version)
storage_mb *= 1024

Expand All @@ -55,6 +55,8 @@ def flexible_server_create(cmd, client,
if high_availability is not None and high_availability.lower() == 'enabled':
if tier == 'Burstable':
raise ArgumentUsageError("High availability is not supported for Burstable tier")
if single_az:
raise ArgumentUsageError("This region is single availability zone. High availability is not supported in a single availability zone region.")

# Raise error when user passes values for both parameters
if subnet_arm_resource_id is not None and public_access is not None:
Expand Down Expand Up @@ -138,7 +140,8 @@ def flexible_server_create(cmd, client,

def flexible_server_restore(cmd, client,
resource_group_name, server_name,
source_server, restore_point_in_time=None, location=None, zone=None, no_wait=False):
source_server, restore_point_in_time=None, location=None, zone=None, no_wait=False,
private_dns_zone_arguments=None):
provider = 'Microsoft.DBforPostgreSQL'
validate_server_name(cf_postgres_check_resource_availability(cmd.cli_ctx, '_'), server_name, 'Microsoft.DBforPostgreSQL/flexibleServers')

Expand Down Expand Up @@ -169,7 +172,19 @@ def flexible_server_restore(cmd, client,
try:
source_server_object = client.get(id_parts['resource_group'], id_parts['name'])
parameters.location = source_server_object.location
parameters.private_dns_zone_arguments = source_server_object.private_dns_zone_arguments
if source_server_object.public_network_access == 'Disabled':
parameters.private_dns_zone_arguments = source_server_object.private_dns_zone_arguments
if private_dns_zone_arguments is not None:
subnet_id = source_server_object.delegated_subnet_arguments.subnet_arm_resource_id
private_dns_zone_id = prepare_private_dns_zone(cmd,
'PostgreSQL',
resource_group_name,
server_name,
private_dns_zone=private_dns_zone_arguments,
subnet_id=subnet_id,
location=location)
parameters.private_dns_zone_arguments = postgresql_flexibleservers.models.ServerPropertiesPrivateDnsZoneArguments(private_dns_zone_arm_resource_id=private_dns_zone_id)

except Exception as e:
raise ResourceNotFoundError(e)

Expand All @@ -190,7 +205,7 @@ def flexible_server_update_custom_func(cmd, instance,

# validator
location = ''.join(instance.location.lower().split())
sku_info = get_postgres_list_skus_info(cmd, location)
sku_info, _ = get_postgres_list_skus_info(cmd, location)
pg_arguments_validator(tier, sku_name, storage_mb, sku_info, instance=instance)

server_module_path = instance.__module__
Expand Down
Loading