Skip to content
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 @@ -165,3 +165,11 @@ def cf_mgmt_file_shares(cli_ctx, _):

def cf_blob_data_gen_update(cli_ctx, kwargs):
return blob_data_service_factory(cli_ctx, kwargs.copy())


def cf_private_link(cli_ctx, _):
return storage_client_factory(cli_ctx).private_link_resources


def cf_private_endpoint(cli_ctx, _):
return storage_client_factory(cli_ctx).private_endpoint_connections
99 changes: 99 additions & 0 deletions src/azure-cli/azure/cli/command_modules/storage/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,105 @@
crafted: true
"""

helps['storage account private-endpoint-connection'] = """
type: group
short-summary: Manage storage account private endpoint connection.
"""

helps['storage account private-endpoint-connection approve'] = """
type: command
short-summary: Approve a private endpoint connection request for storage account.
examples:
- name: Approve a private endpoint connection request for storage account by ID.
text: |
az storage account private-endpoint-connection approve --id "/subscriptions/0000-0000-0000-0000/resourceGroups/MyResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount/privateEndpointConnections/mystorageaccount.b56b5a95-0588-4f8b-b348-15db61590a6c"
- name: Approve a private endpoint connection request for storage account by ID.
text: |
id = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].id")
az storage account private-endpoint-connection approve --id $id
- name: Approve a private endpoint connection request for storage account using account name and connection name.
text: |
az storage account private-endpoint-connection approve -g myRg --account-name mystorageaccount --name myconnection
- name: Approve a private endpoint connection request for storage account using account name and connection name.
text: |
name = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].name")
az storage account private-endpoint-connection approve -g myRg --account-name mystorageaccount --name $name
"""

helps['storage account private-endpoint-connection delete'] = """
type: command
short-summary: Delete a private endpoint connection request for storage account.
examples:
- name: Delete a private endpoint connection request for storage account by ID.
text: |
az storage account private-endpoint-connection delete --id "/subscriptions/0000-0000-0000-0000/resourceGroups/MyResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount/privateEndpointConnections/mystorageaccount.b56b5a95-0588-4f8b-b348-15db61590a6c"
- name: Delete a private endpoint connection request for storage account by ID.
text: |
id = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].id")
az storage account private-endpoint-connection delete --id $id
- name: Delete a private endpoint connection request for storage account using account name and connection name.
text: |
az storage account private-endpoint-connection delete -g myRg --account-name mystorageaccount --name myconnection
- name: Delete a private endpoint connection request for storage account using account name and connection name.
text: |
name = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].name")
az storage account private-endpoint-connection delete -g myRg --account-name mystorageaccount --name $name
"""

helps['storage account private-endpoint-connection reject'] = """
type: command
short-summary: Reject a private endpoint connection request for storage account.
examples:
- name: Reject a private endpoint connection request for storage account by ID.
text: |
az storage account private-endpoint-connection reject --id "/subscriptions/0000-0000-0000-0000/resourceGroups/MyResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount/privateEndpointConnections/mystorageaccount.b56b5a95-0588-4f8b-b348-15db61590a6c"
- name: Reject a private endpoint connection request for storage account by ID.
text: |
id = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].id")
az storage account private-endpoint-connection reject --id $id
- name: Reject a private endpoint connection request for storage account using account name and connection name.
text: |
az storage account private-endpoint-connection reject -g myRg --account-name mystorageaccount --name myconnection
- name: Reject a private endpoint connection request for storage account using account name and connection name.
text: |
name = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].name")
az storage account private-endpoint-connection reject -g myRg --account-name mystorageaccount --name $name
"""

helps['storage account private-endpoint-connection show'] = """
type: command
short-summary: Show details of a private endpoint connection request for storage account.
examples:
- name: Show details of a private endpoint connection request for storage account by ID.
text: |
az storage account private-endpoint-connection show --id "/subscriptions/0000-0000-0000-0000/resourceGroups/MyResourceGroup/providers/Microsoft.Storage/storageAccounts/mystorageaccount/privateEndpointConnections/mystorageaccount.b56b5a95-0588-4f8b-b348-15db61590a6c"
- name: Show details of a private endpoint connection request for storage account by ID.
text: |
id = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].id")
az storage account private-endpoint-connection show --id $id
- name: Show details of a private endpoint connection request for storage account using account name and connection name.
text: |
az storage account private-endpoint-connection show -g myRg --account-name mystorageaccount --name myconnection
- name: Show details of a private endpoint connection request for storage account using account name and connection name.
text: |
name = (az storage account show -n mystorageaccount --query "privateEndpointConnections[0].name")
az storage account private-endpoint-connection show -g myRg --account-name mystorageaccount --name $name
"""

helps['storage account private-link-resource'] = """
type: group
short-summary: Manage storage account private link resources.
"""

helps['storage account private-link-resource list'] = """
type: command
short-summary: Get the private link resources that need to be created for a storage account.
examples:
- name: Get the private link resources that need to be created for a storage account.
text: |
az storage account private-link-resource list --account-name mystorageaccount -g MyResourceGroup
"""

helps['storage account revoke-delegation-keys'] = """
type: command
short-summary: Revoke all user delegation keys for a storage account.
Expand Down
19 changes: 18 additions & 1 deletion src/azure-cli/azure/cli/command_modules/storage/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
validate_azcopy_remove_arguments, as_user_validator, parse_storage_account)


def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statements
def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statements, too-many-lines
from argcomplete.completers import FilesCompleter
from six import u as unicode_string

Expand Down Expand Up @@ -204,6 +204,23 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem
c.argument('publish_microsoft_endpoints', publish_microsoft_endpoints_type)
c.argument('publish_internet_endpoints', publish_internet_endpoints_type)

with self.argument_context('storage account private-endpoint-connection',
resource_type=ResourceType.MGMT_STORAGE) as c:
c.argument('private_endpoint_connection_name', options_list=['--name', '-n'],
help='The name of the private endpoint connection associated with the Storage Account.')
for item in ['approve', 'reject', 'show', 'delete']:
with self.argument_context('storage account private-endpoint-connection {}'.format(item),
resource_type=ResourceType.MGMT_STORAGE) as c:
c.argument('private_endpoint_connection_name', options_list=['--name', '-n'], required=False,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required=False [](start = 90, length = 14)

this is a question, what is the default value for required? from your sample, it seems we can use connection_id or connection_name + account_name +rpName, why are the later parameters marked as required=false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

required means it has to be provided when using the command.
As you know, we have two options to manage the connection. With the two options, connection_id can be none if you use option2, and connection_name+account_name+rgName also can be none if you use option1.
In this way, no one is always required for the command.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why connection_id does not have required=false marked? I suppose required is false by default?

help='The name of the private endpoint connection associated with the Storage Account.')
c.extra('connection_id', options_list=['--id'],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😄

help='The ID of the private endpoint connection associated with the Storage Account. You can get '
'it using `az storage account show`.')
c.argument('account_name', help='The storage account name.', required=False)
c.argument('resource_group_name', help='The resource group name of specified storage account.',
required=False)
c.argument('description', help='Comments for {} operation.'.format(item))

with self.argument_context('storage account update', resource_type=ResourceType.MGMT_STORAGE) as c:
c.register_common_storage_account_options()
c.argument('custom_domain',
Expand Down
23 changes: 20 additions & 3 deletions src/azure-cli/azure/cli/command_modules/storage/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@

# pylint: disable=protected-access

from knack.util import CLIError
from knack.log import get_logger

from azure.cli.core.commands.validators import validate_key_value_pairs
from azure.cli.core.profiles import ResourceType, get_sdk

Expand All @@ -16,7 +19,6 @@
from azure.cli.command_modules.storage.sdkutil import get_table_data_type
from azure.cli.command_modules.storage.url_quote_util import encode_for_url
from azure.cli.command_modules.storage.oauth_token_util import TokenUpdater
from knack.log import get_logger

storage_account_key_options = {'primary': 'key1', 'secondary': 'key2'}
logger = get_logger(__name__)
Expand Down Expand Up @@ -792,7 +794,6 @@ def process_blob_upload_batch_parameters(cmd, namespace):
namespace.blob_type = 'page'
elif any(vhd_files):
# source files contain vhd files but not all of them
from knack.util import CLIError
raise CLIError("""Fail to guess the required blob type. Type of the files to be
uploaded are not consistent. Default blob type for .vhd files is "page", while
others are "block". You can solve this problem by either explicitly set the blob
Expand Down Expand Up @@ -939,7 +940,6 @@ def validate_subnet(cmd, namespace):
child_type_1='subnets',
child_name_1=subnet)
else:
from knack.util import CLIError
raise CLIError('incorrect usage: [--subnet ID | --subnet NAME --vnet-name NAME]')


Expand Down Expand Up @@ -1159,3 +1159,20 @@ def validator_delete_retention_days(namespace):
if namespace.delete_retention_days > 365:
raise ValueError(
"incorrect usage: '--delete-retention-days' must be less than or equal to 365")


def validate_private_endpoint_connection_id(cmd, namespace):
if namespace.connection_id:
from azure.cli.core.util import parse_proxy_resource_id
result = parse_proxy_resource_id(namespace.connection_id)
namespace.resource_group_name = result['resource_group']
namespace.account_name = result['name']
namespace.private_endpoint_connection_name = result['child_name_1']

if namespace.account_name and not namespace.resource_group_name:
namespace.resource_group_name = _query_account_rg(cmd.cli_ctx, namespace.account_name)[0]

if not all([namespace.account_name, namespace.resource_group_name, namespace.private_endpoint_connection_name]):
raise CLIError('incorrect usage: [--id ID | --name NAME --account-name NAME]')

del namespace.connection_id
Copy link
Contributor

@zhoxing-ms zhoxing-ms Mar 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the above throws an exception, what will happen if del namespace.connection_id is not executed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as above

37 changes: 36 additions & 1 deletion src/azure-cli/azure/cli/command_modules/storage/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
multi_service_properties_factory,
cf_mgmt_policy,
cf_blob_data_gen_update, cf_sa_for_keys,
cf_mgmt_blob_services, cf_mgmt_file_shares)
cf_mgmt_blob_services, cf_mgmt_file_shares,
cf_private_link, cf_private_endpoint)
from azure.cli.command_modules.storage.sdkutil import cosmosdb_table_exists
from azure.cli.command_modules.storage._format import transform_immutability_policy
from azure.cli.core.commands import CliCommandType
Expand Down Expand Up @@ -43,6 +44,23 @@ def load_command_table(self, _): # pylint: disable=too-many-locals, too-many-st
resource_type=ResourceType.MGMT_STORAGE
)

private_link_resource_sdk = CliCommandType(
operations_tmpl='azure.mgmt.storage.operations#PrivateLinkResourcesOperations.{}',
client_factory=cf_private_link,
resource_type=ResourceType.MGMT_STORAGE
)

private_endpoint_sdk = CliCommandType(
operations_tmpl='azure.mgmt.storage.operations#PrivateEndpointConnectionsOperations.{}',
client_factory=cf_private_endpoint,
resource_type=ResourceType.MGMT_STORAGE
)

private_endpoint_custom_type = CliCommandType(
operations_tmpl='azure.cli.command_modules.storage.operations.account#{}',
client_factory=cf_private_endpoint,
resource_type=ResourceType.MGMT_STORAGE)

storage_account_custom_type = CliCommandType(
operations_tmpl='azure.cli.command_modules.storage.operations.account#{}',
client_factory=cf_sa)
Expand Down Expand Up @@ -129,6 +147,23 @@ def get_custom_sdk(custom_module, client_factory, resource_type=ResourceType.DAT
g.custom_command('list', 'list_network_rules')
g.custom_command('remove', 'remove_network_rule')

with self.command_group('storage account private-endpoint-connection', private_endpoint_sdk,
custom_command_type=private_endpoint_custom_type, is_preview=True,
resource_type=ResourceType.MGMT_STORAGE, min_api='2019-06-01') as g:
from ._validators import validate_private_endpoint_connection_id
g.command('delete', 'delete', confirmation=True, validator=validate_private_endpoint_connection_id)
g.command('show', 'get', validator=validate_private_endpoint_connection_id)
g.custom_command('approve', 'approve_private_endpoint_connection',
validator=validate_private_endpoint_connection_id)
g.custom_command('reject', 'reject_private_endpoint_connection',
validator=validate_private_endpoint_connection_id)

with self.command_group('storage account private-link-resource', private_link_resource_sdk,
resource_type=ResourceType.MGMT_STORAGE) as g:
from azure.cli.core.commands.transform import gen_dict_to_list_transform
g.command('list', 'list_by_storage_account', is_preview=True, min_api='2019-06-01',
transform=gen_dict_to_list_transform(key="value"))

with self.command_group('storage account blob-service-properties', blob_service_mgmt_sdk,
custom_command_type=storage_account_custom_type,
resource_type=ResourceType.MGMT_STORAGE, min_api='2018-07-01', is_preview=True) as g:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,51 @@ def remove_network_rule(cmd, client, resource_group_name, account_name, ip_addre
return client.update(resource_group_name, account_name, params)


def _update_private_endpoint_connection_status(cmd, client, resource_group_name, account_name,
private_endpoint_connection_name, is_approved=True, description=None):

PrivateEndpointServiceConnectionStatus, ErrorResponseException = \
cmd.get_models('PrivateEndpointServiceConnectionStatus', 'ErrorResponseException')

private_endpoint_connection = client.get(resource_group_name=resource_group_name, account_name=account_name,
private_endpoint_connection_name=private_endpoint_connection_name)

old_status = private_endpoint_connection.private_link_service_connection_state.status
new_status = PrivateEndpointServiceConnectionStatus.approved \
if is_approved else PrivateEndpointServiceConnectionStatus.rejected
private_endpoint_connection.private_link_service_connection_state.status = new_status
private_endpoint_connection.private_link_service_connection_state.description = description
try:
return client.put(resource_group_name=resource_group_name,
account_name=account_name,
private_endpoint_connection_name=private_endpoint_connection_name,
properties=private_endpoint_connection)
except ErrorResponseException as ex:
if ex.response.status_code == 400:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you need raise the exception again after all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch! Thanks!

from msrestazure.azure_exceptions import CloudError
if new_status == "Approved" and old_status == "Rejected":
raise CloudError(ex.response, "You cannot approve the connection request after rejection. "
"Please create a new connection for approval.")
raise ex


def approve_private_endpoint_connection(cmd, client, resource_group_name, account_name,
private_endpoint_connection_name, description=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bim-msft I think it make senses to use description instead of approval_description. It's shorter and be same with the response. Could you change keyvault as same?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bim-msft I think it make senses to use description instead of approval_description. It's shorter and be same with the response. Could you change keyvault as same?

Agree with you, I will modify this as well.


return _update_private_endpoint_connection_status(
cmd, client, resource_group_name=resource_group_name, account_name=account_name, is_approved=True,
private_endpoint_connection_name=private_endpoint_connection_name, description=description
)


def reject_private_endpoint_connection(cmd, client, resource_group_name, account_name, private_endpoint_connection_name,
description=None):
return _update_private_endpoint_connection_status(
cmd, client, resource_group_name=resource_group_name, account_name=account_name, is_approved=False,
private_endpoint_connection_name=private_endpoint_connection_name, description=description
)


def create_management_policies(client, resource_group_name, account_name, policy=None):
if policy:
if os.path.exists(policy):
Expand Down
Loading