Skip to content

Commit

Permalink
[Storage-Preview] az storage account local-user: Add ACL support, a…
Browse files Browse the repository at this point in the history
…nd list paging/filtering (Azure#7795)

* copy local-user from main repo for preview features

* add new params to `az storage account create/update` --extended-groups --is-nfsv3-enabled --group-id --allow-acl-authorization

* remove `--extended-groups` `--is-nfsv3-enabled` from `az storage account create/update` as not fully supported yet

* add list --filter and --maxpagesize

* lint

* move

* retrigger ci
  • Loading branch information
calvinhzy authored Jul 15, 2024
1 parent ae38b1e commit a32751a
Show file tree
Hide file tree
Showing 8 changed files with 1,297 additions and 17 deletions.
4 changes: 4 additions & 0 deletions src/storage-preview/HISTORY.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
Release History
===============

1.0.0b2(2024-07-15)
++++++++++++++++++
* `az storage account local-user`: Support `--group-id` and `--allow-acl-authorization`. Support list paging and filtering

1.0.0b1(2023-08-11)
++++++++++++++++++
* `az storage account migration start/show`: Support start and show storage account migration
Expand Down
75 changes: 75 additions & 0 deletions src/storage-preview/azext_storage_preview/_help.py
Original file line number Diff line number Diff line change
Expand Up @@ -798,3 +798,78 @@
text: |
az storage share close-handle --account-name MyAccount --name MyFileShare --path 'dir1/test.txt' --handle-id "id"
"""

helps['storage account local-user'] = """
type: group
short-summary: Manage storage account local users.
"""

helps['storage account local-user create'] = """
type: command
short-summary: Create a local user for a given storage account.
examples:
- name: Create a local-user with two permission scopes and an ssh-authorized-key
text: >
az storage account local-user create --account-name {account-name} -g {resource-group} -n {username}
--home-directory home --permission-scope permissions=r service=blob resource-name=container1
--permission-scope permissions=rw service=file resource-name=share2 --ssh-authorized-key key="ssh-rsa a2V5"
--has-ssh-key true --has-ssh-password --has-shared-key false --group-id 1 --allow-acl-authorization true
"""

helps['storage account local-user update'] = """
type: command
short-summary: Update properties for a local user.
examples:
- name: Update a local-user with one permission scopes and no ssh-key
text: >
az storage account local-user update --account-name {account-name} -g {resource-group} -n {username}
--permission-scope permissions=rw service=file resource-name=share2
--has-ssh-key false --group-id 2 --allow-acl-authorization false
"""

helps['storage account local-user delete'] = """
type: command
short-summary: Delete a local user.
examples:
- name: Delete a local-user
text: >
az storage account local-user delete --account-name {account-name} -g {resource-group} -n {username}
"""

helps['storage account local-user list'] = """
type: command
short-summary: List local users for a storage account.
examples:
- name: List local-user for a storage account with name starting with test and only returning 3 results
text: >
az storage account local-user list --account-name {account-name} -g {resource-group}
--filter "startswith(name, test)" --maxpagesize 3
"""

helps['storage account local-user show'] = """
type: command
short-summary: Show info for a local user.
examples:
- name: Show info for a local-user
text: >
az storage account local-user show --account-name {account-name} -g {resource-group} -n {username}
"""

helps['storage account local-user list-keys'] = """
type: command
short-summary: List sharedkeys and sshAuthorizedKeys for a local user.
examples:
- name: List sharedkeys and sshAuthorizedKeys for a local-user
text: >
az storage account local-user list-keys --account-name {account-name} -g {resource-group} -n {username}
"""

helps['storage account local-user regenerate-password'] = """
type: command
short-summary: Regenerate sshPassword for a local user.
examples:
- name: Regenerate sshPassword for a local-user
text: >
az storage account local-user regenerate-password --account-name {account-name} -g {resource-group}
-n {username}
"""
42 changes: 41 additions & 1 deletion src/storage-preview/azext_storage_preview/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
validate_storage_data_plane_list, validate_immutability_arguments,
process_resource_group, validate_encryption_source,
get_permission_help_string, get_permission_validator,
add_progress_callback, validate_share_close_handle)
add_progress_callback, validate_share_close_handle,
PermissionScopeAddAction, SshPublicKeyAddAction)

from .profiles import CUSTOM_MGMT_STORAGE, CUSTOM_DATA_STORAGE_FILESHARE

Expand Down Expand Up @@ -909,3 +910,42 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem
c.extra('disallow_source_trailing_dot', arg_type=get_three_state_flag(), default=False, is_preview=True,
options_list=["--disallow-source-trailing-dot", "--disallow-src-trailing"],
help="If true, the trailing dot will be trimmed from the source URI. Default to False")

with self.argument_context('storage account local-user') as c:
c.argument('account_name', acct_name_type, options_list='--account-name', id_part=None)
c.argument('username', options_list=['--user-name', '--name', '-n'],
help='The name of local user. The username must contain lowercase letters and numbers '
'only. It must be unique only within the storage account.')

for item in ['create', 'update']:
with self.argument_context(f'storage account local-user {item}') as c:
c.argument('permission_scope', nargs='+', action=PermissionScopeAddAction,
help='The permission scope argument list which includes the permissions, service, '
'and resource_name.'
'The permissions can be a combination of the below possible values: '
'Read(r), Write (w), Delete (d), List (l), and Create (c). '
'The service has possible values: blob, file. '
'The resource-name is the container name or the file share name. '
'Example: --permission-scope permissions=r service=blob resource-name=container1'
'Can specify multiple permission scopes: '
'--permission-scope permissions=rw service=blob resource-name=container1'
'--permission-scope permissions=rwd service=file resource-name=share2')
c.argument('home_directory', help='The home directory.')
c.argument('ssh_authorized_key', nargs='+', action=SshPublicKeyAddAction,
help='SSH authorized keys for SFTP. Includes an optional description and key. '
'The key is the base64 encoded SSH public key , with format: '
'<keyType> <keyData> e.g. ssh-rsa AAAABBBB.'
'Example: --ssh_authorized_key description=description key="ssh-rsa AAAABBBB"'
'or --ssh_authorized_key key="ssh-rsa AAAABBBB"')
c.argument('has_shared_key', arg_type=get_three_state_flag(),
help='Indicates whether shared key exists. Set it to false to remove existing shared key.')
c.argument('has_ssh_key', arg_type=get_three_state_flag(),
help='Indicates whether ssh key exists. Set it to false to remove existing SSH key.')
c.argument('has_ssh_password', arg_type=get_three_state_flag(),
help='Indicates whether ssh password exists. Set it to false to remove existing SSH password.')
c.argument('group_id',
help='An identifier for associating a group of users.')
c.argument('allow_acl_authorization', options_list=['--allow-acl-authorization', '--allow-acl-auth'],
arg_type=get_three_state_flag(),
help='Indicates whether ACL authorization is allowed for this user. '
'Set it to false to disallow using ACL authorization.')
39 changes: 24 additions & 15 deletions src/storage-preview/azext_storage_preview/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from azure.cli.core.commands.client_factory import get_mgmt_service_client
from azure.cli.core.commands.validators import validate_key_value_pairs
from azure.cli.core.profiles import get_sdk
from azure.cli.core.azclierror import UnrecognizedArgumentError
from knack.util import CLIError
from knack.log import get_logger
from ._client_factory import get_storage_data_service_client, blob_data_service_factory, cf_blob_service, \
Expand Down Expand Up @@ -668,14 +669,18 @@ def __call__(self, parser, namespace, values, option_string=None):
try:
permissions, service, resource_name = '', '', ''
for s in values:
if "permissions" in s:
permissions = s.split('=')[1]
elif "service" in s:
service = s.split('=')[1]
elif "resource-name" in s:
resource_name = s.split('=')[1]
except (ValueError, TypeError):
raise CLIError('usage error: --permission-scope VARIABLE OPERATOR VALUE')
k, v = s.split('=', 1)
if k == "permissions":
permissions = v
elif k == "service":
service = v
elif k == "resource-name":
resource_name = v
else:
raise UnrecognizedArgumentError(
'key error: key must be one of permissions, service, resource-name for --permission-scope')
except (ValueError, TypeError, IndexError):
raise CLIError('usage error: --permission-scope [Key=Value ...]')
namespace.permission_scope.append(PermissionScope(
permissions=permissions,
service=service,
Expand All @@ -691,13 +696,17 @@ def __call__(self, parser, namespace, values, option_string=None):
SshPublicKey = namespace._cmd.get_models('SshPublicKey')
try:
description, key = '', ''
for k in values:
if "description" in k:
description = k.split('=')[1]
elif "key" in k:
key = k.split('=')[1]
except (ValueError, TypeError):
raise CLIError('usage error: --ssh-authorized-key VARIABLE OPERATOR VALUE')
for s in values:
k, v = s.split('=', 1)
if k == "description":
description = v
elif k == "key":
key = v
else:
raise UnrecognizedArgumentError(
'key error: key must be one of description, key for --ssh-authorized-key')
except (ValueError, TypeError, IndexError):
raise CLIError('usage error: --ssh-authorized-key [Key=Value ...]')
namespace.ssh_authorized_key.append(SshPublicKey(description=description, key=key))


Expand Down
26 changes: 25 additions & 1 deletion src/storage-preview/azext_storage_preview/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from azure.cli.core.commands import CliCommandType
from azure.cli.core.commands.arm import show_exception_handler
from ._client_factory import (cf_sa, blob_data_service_factory, adls_blob_data_service_factory,
cf_share_client, cf_share_file_client, cf_share_directory_client)
cf_share_client, cf_share_file_client, cf_share_directory_client, cf_local_users)
from .profiles import (CUSTOM_DATA_STORAGE, CUSTOM_DATA_STORAGE_ADLS, CUSTOM_MGMT_STORAGE,
CUSTOM_DATA_STORAGE_FILESHARE)

Expand Down Expand Up @@ -213,3 +213,27 @@ def _adls_deprecate_message(self):
exception_handler=file_related_exception_handler,
transform=transform_file_show_result)
g.storage_custom_command('download-batch', 'storage_file_download_batch', client_factory=cf_share_client)

local_users_sdk = CliCommandType(
operations_tmpl='azext_storage_preview.vendored_sdks.azure_mgmt_storage.operations#'
'LocalUsersOperations.{}',
client_factory=cf_local_users,
resource_type=CUSTOM_MGMT_STORAGE
)

local_users_custom_type = CliCommandType(
operations_tmpl='azext_storage_preview.operations.account#{}',
client_factory=cf_local_users,
resource_type=CUSTOM_MGMT_STORAGE
)

with self.command_group('storage account local-user', local_users_sdk,
custom_command_type=local_users_custom_type,
resource_type=CUSTOM_MGMT_STORAGE, min_api='2021-08-01', is_preview=True) as g:
g.custom_command('create', 'create_local_user')
g.custom_command('update', 'update_local_user')
g.command('delete', 'delete')
g.command('list', 'list')
g.show_command('show', 'get')
g.command('list-keys', 'list_keys')
g.command('regenerate-password', 'regenerate_password')
46 changes: 46 additions & 0 deletions src/storage-preview/azext_storage_preview/operations/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -554,3 +554,49 @@ def update_storage_account(cmd, instance, sku=None, tags=None, custom_domain=Non
params.is_local_user_enabled = enable_local_user

return params


def _generate_local_user(local_user, permission_scope=None, ssh_authorized_key=None,
home_directory=None, has_shared_key=None, has_ssh_key=None, has_ssh_password=None,
group_id=None, allow_acl_authorization=None):
if permission_scope is not None:
local_user.permission_scopes = permission_scope
if ssh_authorized_key is not None:
local_user.ssh_authorized_keys = ssh_authorized_key
if home_directory is not None:
local_user.home_directory = home_directory
if has_shared_key is not None:
local_user.has_shared_key = has_shared_key
if has_ssh_key is not None:
local_user.has_ssh_key = has_ssh_key
if has_ssh_password is not None:
local_user.has_ssh_password = has_ssh_password
if group_id is not None:
local_user.group_id = group_id
if allow_acl_authorization is not None:
local_user.allow_acl_authorization = allow_acl_authorization


def create_local_user(cmd, client, resource_group_name, account_name, username, permission_scope=None, home_directory=None,
has_shared_key=None, has_ssh_key=None, has_ssh_password=None, ssh_authorized_key=None,
group_id=None, allow_acl_authorization=None):
LocalUser = cmd.get_models('LocalUser')
local_user = LocalUser()

_generate_local_user(local_user, permission_scope, ssh_authorized_key,
home_directory, has_shared_key, has_ssh_key, has_ssh_password, group_id,
allow_acl_authorization)
return client.create_or_update(resource_group_name=resource_group_name, account_name=account_name,
username=username, properties=local_user)


def update_local_user(cmd, client, resource_group_name, account_name, username, permission_scope=None,
home_directory=None, has_shared_key=None, has_ssh_key=None, has_ssh_password=None,
ssh_authorized_key=None, group_id=None, allow_acl_authorization=None):
local_user = client.get(resource_group_name, account_name, username)

_generate_local_user(local_user, permission_scope, ssh_authorized_key,
home_directory, has_shared_key, has_ssh_key, has_ssh_password, group_id,
allow_acl_authorization)
return client.create_or_update(resource_group_name=resource_group_name, account_name=account_name,
username=username, properties=local_user)
Loading

0 comments on commit a32751a

Please sign in to comment.