Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/horizondb/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@
Release History
===============

1.0.0b5
+++++++
* Add support for configuring public access on HorizonDB clusters through `az horizondb create --public-access` and `az horizondb update --public-access`. Supplying an IP address or range automatically creates a firewall rule.
* Add the `az horizondb firewall-rule` command group (`create`, `show`, `list`, `update`, `delete`) to manage cluster firewall rules.

1.0.0b4
+++++++
* Update validation checks for commands. Add short form arguments for user convenience.
Expand Down
4 changes: 4 additions & 0 deletions src/horizondb/azext_horizondb/_client_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,7 @@ def resource_client_factory(cli_ctx, subscription_id=None):

def cf_horizondb_clusters(cli_ctx, _):
return get_horizondb_management_client(cli_ctx).horizon_db_clusters


def cf_horizondb_firewall_rules(cli_ctx, _):
return get_horizondb_management_client(cli_ctx).horizon_db_firewall_rules
66 changes: 66 additions & 0 deletions src/horizondb/azext_horizondb/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@
text: az horizondb create --name examplecluster --resource-group exampleresourcegroup --location centralus --administrator-login myadmin --administrator-login-password examplepassword --version 17 --v-cores 4 --replica-count 3
- name: Create a HorizonDB cluster with zone placement policy.
text: az horizondb create --name examplecluster --resource-group exampleresourcegroup --location centralus --administrator-login myadmin --administrator-login-password examplepassword --version 17 --v-cores 4 --replica-count 3 --zone-placement-policy Strict
- name: Create a HorizonDB cluster and allow public access from a single IP address (creates a firewall rule).
text: az horizondb create --name examplecluster --resource-group exampleresourcegroup --location centralus --administrator-login myadmin --administrator-login-password examplepassword --version 17 --v-cores 4 --public-access 12.12.12.12
- name: Create a HorizonDB cluster and allow public access from a range of IP addresses.
text: az horizondb create --name examplecluster --resource-group exampleresourcegroup --location centralus --administrator-login myadmin --administrator-login-password examplepassword --version 17 --v-cores 4 --public-access 12.12.12.0-12.12.12.255
- name: Create a HorizonDB cluster and allow public access from all IP addresses.
text: az horizondb create --name examplecluster --resource-group exampleresourcegroup --location centralus --administrator-login myadmin --administrator-login-password examplepassword --version 17 --v-cores 4 --public-access All
"""


Expand All @@ -34,6 +40,8 @@
text: az horizondb update --name examplecluster --resource-group exampleresourcegroup --v-cores 6
- name: Assign a parameter group to an existing HorizonDB cluster.
text: az horizondb update --name examplecluster --resource-group exampleresourcegroup --parameter-group /subscriptions/{subscriptionId}/resourceGroups/{resourceGroup}/providers/Microsoft.HorizonDb/parameterGroups/{parameterGroup}
- name: Enable public access on an existing HorizonDB cluster (detects your client IP and prompts to create a firewall rule).
text: az horizondb update --name examplecluster --resource-group exampleresourcegroup --public-access Enabled
"""


Expand Down Expand Up @@ -64,3 +72,61 @@
- name: List Azure HorizonDB clusters in a resource group.
text: az horizondb list --resource-group exampleresourcegroup
"""


helps['horizondb firewall-rule'] = """
type: group
short-summary: Manage firewall rules for an Azure HorizonDB cluster.
long-summary: >
Firewall rules control public access to a HorizonDB cluster and are applied to the cluster's
default pool. Use these commands to allow inbound connections from specific IP addresses or ranges.
"""


helps['horizondb firewall-rule create'] = """
type: command
short-summary: Create a firewall rule for an Azure HorizonDB cluster.
examples:
- name: Create a firewall rule allowing a single IP address.
text: az horizondb firewall-rule create --resource-group exampleresourcegroup --cluster-name examplecluster --name allowclientip --start-ip-address 12.12.12.12
- name: Create a firewall rule allowing a range of IP addresses.
text: az horizondb firewall-rule create --resource-group exampleresourcegroup --cluster-name examplecluster --name allowrange --start-ip-address 12.12.12.0 --end-ip-address 12.12.12.255
- name: Create a firewall rule allowing access from all Azure-internal IP addresses.
text: az horizondb firewall-rule create --resource-group exampleresourcegroup --cluster-name examplecluster --name allowazure --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0
"""


helps['horizondb firewall-rule update'] = """
type: command
short-summary: Update a firewall rule for an Azure HorizonDB cluster.
examples:
- name: Update the IP range of an existing firewall rule.
text: az horizondb firewall-rule update --resource-group exampleresourcegroup --cluster-name examplecluster --name allowrange --start-ip-address 12.12.12.0 --end-ip-address 12.12.12.128
"""


helps['horizondb firewall-rule show'] = """
type: command
short-summary: Show the details of a firewall rule for an Azure HorizonDB cluster.
examples:
- name: Show a firewall rule.
text: az horizondb firewall-rule show --resource-group exampleresourcegroup --cluster-name examplecluster --name allowclientip
"""


helps['horizondb firewall-rule list'] = """
type: command
short-summary: List the firewall rules for an Azure HorizonDB cluster.
examples:
- name: List all firewall rules for a cluster.
text: az horizondb firewall-rule list --resource-group exampleresourcegroup --cluster-name examplecluster
"""


helps['horizondb firewall-rule delete'] = """
type: command
short-summary: Delete a firewall rule for an Azure HorizonDB cluster.
examples:
- name: Delete a firewall rule.
text: az horizondb firewall-rule delete --resource-group exampleresourcegroup --cluster-name examplecluster --name allowclientip
"""
73 changes: 72 additions & 1 deletion src/horizondb/azext_horizondb/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
get_enum_type)
from azure.cli.core.local_context import LocalContextAttribute, LocalContextAction
from .utils.validators import (
validate_replica_count)
validate_replica_count,
public_access_validator,
ip_address_validator)


def load_arguments(self, _): # pylint: disable=too-many-statements, too-many-locals
Expand Down Expand Up @@ -72,6 +74,52 @@ def _horizondb_params():
options_list=['--parameter-group'],
help='The resource ID of the parameter group.')

public_access_create_arg_type = CLIArgumentType(
options_list=['--public-access'],
validator=public_access_validator,
help="Determines the public access for the cluster by creating a firewall rule on the "
"default pool. Enter a single IP address or a range of IP addresses (dash-separated, "
"no spaces) to be included in the allowed list of IPs. Specifying 'All' allows public "
"access from any IP (0.0.0.0-255.255.255.255). 'Enabled' detects your current client "
"IP and prompts to allow it. 'None' and 'Disabled' do not create a firewall rule. "
"Acceptable values: 'Enabled', 'Disabled', 'All', 'None', '{startIP}' and "
"'{startIP}-{endIP}' where each IP ranges from 0.0.0.0 to 255.255.255.255.")

public_access_update_arg_type = CLIArgumentType(
options_list=['--public-access'],
arg_type=get_enum_type(['Enabled', 'Disabled']),
help="Enable or disable public access on the cluster. 'Enabled' detects your current "
"client IP and prompts to create a firewall rule on the default pool. 'Disabled' "
"points you to the 'az horizondb firewall-rule' commands to remove public access.")

firewall_cluster_name_arg_type = CLIArgumentType(
options_list=['--cluster-name', '-c'],
id_part=None,
help='Name of the HorizonDB cluster.')

firewall_rule_name_arg_type = CLIArgumentType(
options_list=['--name', '-n'],
id_part=None,
help='The name of the firewall rule.')

pool_name_arg_type = CLIArgumentType(
options_list=['--pool-name'],
help='The name of the pool the firewall rule targets. Defaults to the default pool.')

start_ip_address_arg_type = CLIArgumentType(
options_list=['--start-ip-address'],
help='The start IP address of the firewall rule (IPv4). Must be dotted-quad format. Use '
'0.0.0.0 to represent all Azure-internal IP addresses.')

end_ip_address_arg_type = CLIArgumentType(
options_list=['--end-ip-address'],
help='The end IP address of the firewall rule (IPv4). Must be dotted-quad format. Use '
'0.0.0.0 to represent all Azure-internal IP addresses.')

firewall_rule_description_arg_type = CLIArgumentType(
options_list=['--description'],
help='The description of the firewall rule.')

with self.argument_context('horizondb') as c:
c.argument('resource_group_name', arg_type=resource_group_name_type)
c.argument('cluster_name', arg_type=cluster_name_arg_type)
Expand All @@ -85,14 +133,37 @@ def _horizondb_params():
c.argument('replica_count', arg_type=replica_count_arg_type)
c.argument('v_cores', arg_type=v_cores_arg_type)
c.argument('zone_placement_policy', arg_type=zone_placement_policy_arg_type)
c.argument('public_access', arg_type=public_access_create_arg_type)
c.argument('yes', arg_type=yes_arg_type)

with self.argument_context('horizondb update') as c:
c.argument('tags', tags_type)
c.argument('administrator_login_password', arg_type=administrator_login_password_arg_type)
c.argument('v_cores', arg_type=v_cores_arg_type)
c.argument('parameter_group', arg_type=parameter_group_arg_type)
c.argument('public_access', arg_type=public_access_update_arg_type)
c.argument('yes', arg_type=yes_arg_type)

with self.argument_context('horizondb delete') as c:
c.argument('yes', arg_type=yes_arg_type)

with self.argument_context('horizondb firewall-rule') as c:
c.argument('resource_group_name', arg_type=resource_group_name_type)
c.argument('cluster_name', arg_type=firewall_cluster_name_arg_type)
c.argument('firewall_rule_name', arg_type=firewall_rule_name_arg_type)
c.argument('pool_name', arg_type=pool_name_arg_type)

with self.argument_context('horizondb firewall-rule create') as c:
c.argument('start_ip_address', arg_type=start_ip_address_arg_type, validator=ip_address_validator)
c.argument('end_ip_address', arg_type=end_ip_address_arg_type)
c.argument('description', arg_type=firewall_rule_description_arg_type)

with self.argument_context('horizondb firewall-rule update') as c:
c.argument('start_ip_address', arg_type=start_ip_address_arg_type, validator=ip_address_validator)
c.argument('end_ip_address', arg_type=end_ip_address_arg_type)
c.argument('description', arg_type=firewall_rule_description_arg_type)

with self.argument_context('horizondb firewall-rule delete') as c:
c.argument('yes', arg_type=yes_arg_type)

_horizondb_params()
17 changes: 16 additions & 1 deletion src/horizondb/azext_horizondb/cluster_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from azure.cli.core.commands import CliCommandType
from azext_horizondb._client_factory import (
cf_horizondb_clusters)
cf_horizondb_clusters,
cf_horizondb_firewall_rules)
from azext_horizondb.utils._transformers import (
table_transform_output)

Expand All @@ -19,6 +20,11 @@ def load_command_table(self, _):

custom_commands = CliCommandType(
operations_tmpl='azext_horizondb.commands.custom_commands#{}')

firewall_rule_custom = CliCommandType(
operations_tmpl='azext_horizondb.commands.firewall_rule_commands#{}',
client_factory=cf_horizondb_firewall_rules)

with self.command_group('horizondb', horizondb_clusters_sdk,
custom_command_type=custom_commands,
client_factory=cf_horizondb_clusters) as g:
Expand All @@ -27,3 +33,12 @@ def load_command_table(self, _):
g.custom_command('delete', 'horizondb_cluster_delete')
g.custom_command('list', 'horizondb_cluster_list')
g.show_command('show', 'get')

with self.command_group('horizondb firewall-rule', firewall_rule_custom,
custom_command_type=firewall_rule_custom,
client_factory=cf_horizondb_firewall_rules) as g:
g.custom_command('create', 'horizondb_firewall_rule_create')
g.custom_command('update', 'horizondb_firewall_rule_update')
g.custom_command('delete', 'horizondb_firewall_rule_delete')
g.custom_show_command('show', 'horizondb_firewall_rule_get')
g.custom_command('list', 'horizondb_firewall_rule_list')
62 changes: 51 additions & 11 deletions src/horizondb/azext_horizondb/commands/custom_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from ..utils._util import (
check_resource_group,
generate_missing_cluster_parameters)
from ..utils._network import resolve_public_access_range

logger = get_logger(__name__)

Expand All @@ -26,6 +27,7 @@ def horizondb_cluster_create(cmd, client, resource_group_name=None, cluster_name
tags=None, version=None,
replica_count=None, v_cores=None,
zone_placement_policy=None,
public_access=None, yes=False,
no_wait=False):
from azext_horizondb.vendored_sdks.models import HorizonDbCluster, HorizonDbClusterProperties

Expand Down Expand Up @@ -67,16 +69,46 @@ def horizondb_cluster_create(cmd, client, resource_group_name=None, cluster_name
properties=properties,
)

return sdk_no_wait(no_wait, client.begin_create_or_update,
resource_group_name=resource_group_name,
cluster_name=cluster_name,
resource=resource)
result = sdk_no_wait(no_wait, client.begin_create_or_update,
resource_group_name=resource_group_name,
cluster_name=cluster_name,
resource=resource)

# When --public-access supplies an IP range, create a firewall rule once the cluster exists.
# HorizonDB's publicNetworkAccess flag is service-computed (read-only), so a firewall rule is
# the mechanism for opening public access.
if public_access is None:
return result

def horizondb_cluster_update(client, resource_group_name, cluster_name,
cluster = result.result() if hasattr(result, 'result') else result
_apply_public_access(cmd, resource_group_name, cluster_name, public_access, yes)
return cluster


def _apply_public_access(cmd, resource_group_name, cluster_name, public_access, yes):
val = str(public_access).lower()
if val == 'disabled':
logger.warning("HorizonDB public network access is managed through firewall rules. To remove "
"public access, delete rules with 'az horizondb firewall-rule delete' "
"(list them with 'az horizondb firewall-rule list').")
return
Comment on lines +90 to +94

start_ip, end_ip = resolve_public_access_range(public_access, yes)
if start_ip == -1 or end_ip == -1:
return

from .._client_factory import cf_horizondb_firewall_rules
from .firewall_rule_commands import create_firewall_rule
firewall_client = cf_horizondb_firewall_rules(cmd.cli_ctx, None)
create_firewall_rule(cmd, firewall_client, resource_group_name, cluster_name,
start_ip_address=start_ip, end_ip_address=end_ip).result()


def horizondb_cluster_update(cmd, client, resource_group_name, cluster_name,
administrator_login_password=None, tags=None,
v_cores=None,
parameter_group=None,
public_access=None, yes=False,
no_wait=False):
from azext_horizondb.vendored_sdks.models import (
HorizonDbClusterForPatchUpdate,
Expand All @@ -100,15 +132,23 @@ def horizondb_cluster_update(client, resource_group_name, cluster_name,
if cluster_properties:
patch_properties["properties"] = HorizonDbClusterPropertiesForPatchUpdate(**cluster_properties)

if not patch_properties:
if not patch_properties and public_access is None:
raise ArgumentUsageError("Specify at least one argument to update.")

properties = HorizonDbClusterForPatchUpdate(**patch_properties)
update_result = None
if patch_properties:
properties = HorizonDbClusterForPatchUpdate(**patch_properties)
update_result = sdk_no_wait(no_wait, client.begin_update,
resource_group_name=resource_group_name,
cluster_name=cluster_name,
properties=properties)

if public_access is not None:
_apply_public_access(cmd, resource_group_name, cluster_name, public_access, yes)

return sdk_no_wait(no_wait, client.begin_update,
resource_group_name=resource_group_name,
cluster_name=cluster_name,
properties=properties)
if update_result is not None:
return update_result
return client.get(resource_group_name=resource_group_name, cluster_name=cluster_name)
Comment on lines +146 to +151


def horizondb_cluster_delete(cmd, client, resource_group_name, cluster_name, no_wait=False, yes=False):
Expand Down
Loading
Loading