Skip to content

Commit

Permalink
[Storage] Add allow-protected-append-writes-all to container immutabi…
Browse files Browse the repository at this point in the history
…lity-policy and legal-hold (#19698)

* add allow_protected_append_writes_all to storage container immutability-policy create

* add to legal hold

* make allow_protected_append_writes_all optional for legal hold

* add tests

* lint, fix test
  • Loading branch information
calvinhzy authored Sep 28, 2021
1 parent 9c68a67 commit 26aa3f1
Show file tree
Hide file tree
Showing 10 changed files with 1,132 additions and 93 deletions.
20 changes: 20 additions & 0 deletions src/azure-cli/azure/cli/command_modules/storage/_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,18 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem
'and any existing blocks cannot be modified or deleted. '
'This property cannot be changed with '
'ExtendImmutabilityPolicy API.')
c.extra('allow_protected_append_writes_all', options_list=['--allow-protected-append-writes-all',
'--w-all'],
arg_type=get_three_state_flag(), help="This property can only be changed for unlocked time-based "
"retention policies. When enabled, new blocks can be written "
"to both 'Append and Block Blobs' while maintaining "
"immutability protection and compliance. "
"Only new blocks can be added and any existing blocks cannot "
"be modified or deleted. This property cannot be changed with"
" ExtendImmutabilityPolicy API. The "
"'allowProtectedAppendWrites' and "
"'allowProtectedAppendWritesAll' properties are mutually "
"exclusive.")
c.extra('period', type=int, help='The immutability period for the blobs in the container since the policy '
'creation, in days.')
c.ignore('parameters')
Expand Down Expand Up @@ -1227,6 +1239,14 @@ def load_arguments(self, _): # pylint: disable=too-many-locals, too-many-statem
c.argument('tags', nargs='+',
help='Space-separated tags. Each tag should be 3 to 23 alphanumeric characters and is normalized '
'to lower case')
for item in ['set', 'clear']:
with self.argument_context(f'storage container legal-hold {item}') as c:
c.extra('allow_protected_append_writes_all', options_list=['--allow-protected-append-writes-all',
'--w-all'],
arg_type=get_three_state_flag(),
help="When enabled, new blocks can be written to both Append and Block Blobs while maintaining "
"legal hold protection and compliance. Only new blocks can be added and any existing blocks "
"cannot be modified or deleted.")

with self.argument_context('storage container policy') as c:
from .completers import get_storage_acl_name_completion_list
Expand Down
11 changes: 11 additions & 0 deletions src/azure-cli/azure/cli/command_modules/storage/_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -1871,6 +1871,17 @@ def validate_policy(namespace):
"policy.")


def validate_allow_protected_append_writes_all(namespace):
from azure.cli.core.azclierror import InvalidArgumentValueError
if namespace.allow_protected_append_writes_all and namespace.allow_protected_append_writes:
raise InvalidArgumentValueError("usage error: The 'allow-protected-append-writes' "
"and 'allow-protected-append-writes-all' "
"properties are mutually exclusive. 'allow-protected-append-writes-all' allows "
"new blocks to be written to both Append and Block Blobs, while "
"'allow-protected-append-writes' allows new blocks to be written to "
"Append Blobs only.")


def validate_blob_name_for_upload(namespace):
if not namespace.blob_name:
namespace.blob_name = os.path.basename(namespace.file_path)
6 changes: 5 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 @@ -509,9 +509,13 @@ def get_custom_sdk(custom_module, client_factory, resource_type=ResourceType.DAT
resource_type=ResourceType.MGMT_STORAGE),
min_api='2018-02-01') as g:
from azure.cli.command_modules.storage._transformers import transform_immutability_policy

from ._validators import validate_allow_protected_append_writes_all

g.show_command('show', 'get_immutability_policy',
transform=transform_immutability_policy)
g.custom_command('create', 'create_or_update_immutability_policy')
g.custom_command('create', 'create_or_update_immutability_policy',
validator=validate_allow_protected_append_writes_all)
g.command('delete', 'delete_immutability_policy',
transform=lambda x: None)
g.command('lock', 'lock_immutability_policy')
Expand Down
18 changes: 12 additions & 6 deletions src/azure-cli/azure/cli/command_modules/storage/operations/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,34 +24,40 @@
logger = get_logger(__name__)


def set_legal_hold(cmd, client, container_name, account_name, tags, resource_group_name=None):
def set_legal_hold(cmd, client, container_name, account_name, tags, allow_protected_append_writes_all,
resource_group_name=None):
LegalHold = cmd.get_models('LegalHold', resource_type=ResourceType.MGMT_STORAGE)
legal_hold = LegalHold(tags=tags)
legal_hold = LegalHold(tags=tags, allow_protected_append_writes_all=allow_protected_append_writes_all)
return client.set_legal_hold(resource_group_name, account_name, container_name, legal_hold)


def clear_legal_hold(cmd, client, container_name, account_name, tags, resource_group_name=None):
def clear_legal_hold(cmd, client, container_name, account_name, tags, allow_protected_append_writes_all,
resource_group_name=None):
LegalHold = cmd.get_models('LegalHold', resource_type=ResourceType.MGMT_STORAGE)
legal_hold = LegalHold(tags=tags)
legal_hold = LegalHold(tags=tags, allow_protected_append_writes_all=allow_protected_append_writes_all)
return client.clear_legal_hold(resource_group_name, account_name, container_name, legal_hold)


def create_or_update_immutability_policy(cmd, client, container_name, account_name,
resource_group_name=None, allow_protected_append_writes=None,
allow_protected_append_writes_all=None,
period=None, if_match=None):
ImmutabilityPolicy = cmd.get_models('ImmutabilityPolicy', resource_type=ResourceType.MGMT_STORAGE)
immutability_policy = ImmutabilityPolicy(immutability_period_since_creation_in_days=period,
allow_protected_append_writes=allow_protected_append_writes)
allow_protected_append_writes=allow_protected_append_writes,
allow_protected_append_writes_all=allow_protected_append_writes_all)
return client.create_or_update_immutability_policy(resource_group_name, account_name, container_name,
if_match, immutability_policy)


def extend_immutability_policy(cmd, client, container_name, account_name, if_match,
resource_group_name=None, allow_protected_append_writes=None,
allow_protected_append_writes_all=None,
period=None):
ImmutabilityPolicy = cmd.get_models('ImmutabilityPolicy', resource_type=ResourceType.MGMT_STORAGE)
immutability_policy = ImmutabilityPolicy(immutability_period_since_creation_in_days=period,
allow_protected_append_writes=allow_protected_append_writes)
allow_protected_append_writes=allow_protected_append_writes,
allow_protected_append_writes_all=allow_protected_append_writes_all)
return client.extend_immutability_policy(resource_group_name, account_name, container_name,
if_match, immutability_policy)

Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ def test_storage_container_vlm_scenarios(self, resource_group, storage_account):
JMESPathCheck('name', self.kwargs['container2']),
JMESPathCheck('immutableStorageWithVersioning.enabled', None)})

self.cmd('storage container immutability-policy create -c {container2} --account-name {sa} -w --period 1',
self.cmd('storage container immutability-policy create -c {container2} --account-name {sa} -g {rg} -w --period 1',
checks={
JMESPathCheck('name', self.kwargs['container2']),
JMESPathCheck('immutabilityPeriodSinceCreationInDays', 1)})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.testsdk import (ScenarioTest, JMESPathCheck, ResourceGroupPreparer, StorageAccountPreparer)
from azure.cli.testsdk import (ScenarioTest, JMESPathCheck, ResourceGroupPreparer, StorageAccountPreparer, api_version_constraint)
from azure.core.exceptions import HttpResponseError
from azure_devtools.scenario_tests import AllowLargeResponse
from azure.cli.core.profiles import ResourceType


class StorageImmutabilityPolicy(ScenarioTest):
Expand Down Expand Up @@ -64,3 +65,63 @@ def test_immutability_policy(self, resource_group, storage_account):

self.cmd('az storage container delete --account-name {} -n {} --bypass-immutability-policy'.format(
storage_account, container_name))

@AllowLargeResponse()
@ResourceGroupPreparer()
@StorageAccountPreparer(kind='StorageV2', name_prefix='clitest', location='eastus2euap')
@api_version_constraint(resource_type=ResourceType.MGMT_STORAGE, min_api='2021-06-01')
def test_immutability_policy_with_allow_protected_append_writes_all(self, resource_group, storage_account):
container_name = 'container1'
self.cmd('storage container create --account-name {} -n {}'.format(storage_account, container_name))

self.cmd('az storage container immutability-policy create --account-name {} -c {} -g {} --period 1 --w-all'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck('immutabilityPeriodSinceCreationInDays', 1),
JMESPathCheck('allowProtectedAppendWritesAll', True),
JMESPathCheck('allowProtectedAppendWrites', None)
])

# cannot specific both --w-all and -w
from azure.cli.core.azclierror import InvalidArgumentValueError
with self.assertRaises(InvalidArgumentValueError):
self.cmd(
'az storage container immutability-policy create --account-name {} -c {} -g {} --period 1 --w-all -w'.format(
storage_account, container_name, resource_group))

policy_etag = self.cmd('az storage container immutability-policy show --account-name {} -c {} -g {}'.format(
storage_account, container_name, resource_group)).get_output_in_json().get('etag')

self.cmd('az storage container immutability-policy delete --account-name {} -c {} -g {} --if-match {}'.format(
storage_account, container_name, resource_group, repr(policy_etag)))

self.cmd(
'az storage container immutability-policy create --account-name {} -c {} -g {} --period 1 --w-all false'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck('immutabilityPeriodSinceCreationInDays', 1),
JMESPathCheck('allowProtectedAppendWritesAll', False),
JMESPathCheck('allowProtectedAppendWrites', None)
])

self.cmd(
'az storage container immutability-policy create --account-name {} -c {} -g {} --w-all'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck('immutabilityPeriodSinceCreationInDays', 1),
JMESPathCheck('allowProtectedAppendWritesAll', True),
JMESPathCheck('allowProtectedAppendWrites', None)
])

policy_etag = self.cmd('az storage container immutability-policy show --account-name {} -c {} -g {}'.format(
storage_account, container_name, resource_group)).get_output_in_json().get('etag')

self.cmd('az storage container immutability-policy lock --account-name {} -c {} -g {} --if-match {}'.format(
storage_account, container_name, resource_group, repr(policy_etag)))

policy_etag = self.cmd('az storage container immutability-policy show --account-name {} -c {} -g {}'.format(
storage_account, container_name, resource_group)).get_output_in_json().get('etag')

self.cmd('az storage container immutability-policy extend --account-name {} -c {} -g {} --period 5 --if-match {}'.format(
storage_account, container_name, resource_group, repr(policy_etag)), checks=[
JMESPathCheck('immutabilityPeriodSinceCreationInDays', 5),
JMESPathCheck('allowProtectedAppendWritesAll', True),
JMESPathCheck('allowProtectedAppendWrites', None)
])
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
# Licensed under the MIT License. See License.txt in the project root for license information.
# --------------------------------------------------------------------------------------------

from azure.cli.testsdk import (ScenarioTest, JMESPathCheck, ResourceGroupPreparer, StorageAccountPreparer)
from azure.cli.testsdk import (ScenarioTest, JMESPathCheck, ResourceGroupPreparer, StorageAccountPreparer, api_version_constraint)
from azure_devtools.scenario_tests import AllowLargeResponse
from azure.cli.core.profiles import ResourceType


class StorageLegalHold(ScenarioTest):
Expand All @@ -29,3 +30,35 @@ def test_legal_hold(self, resource_group):
self.cmd('storage container legal-hold clear --account-name {} -c {} -g {} --tags tag1 tag2'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck("tags", [])])

@AllowLargeResponse()
@ResourceGroupPreparer()
@StorageAccountPreparer(kind='StorageV2', name_prefix='clitest', location='eastus2euap')
@api_version_constraint(resource_type=ResourceType.MGMT_STORAGE, min_api='2021-06-01')
def test_legal_hold_with_allow_protected_append_writes_all(self, resource_group, storage_account):
container_name = 'container1'
self.cmd('storage container create --account-name {} -n {} --metadata k1=v1 k2=v2'.format(storage_account,
container_name))
self.cmd('storage container legal-hold show --account-name {} -c {} -g {}'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck("tags", []),
JMESPathCheck("allowProtectedAppendWritesAll", None)
])

self.cmd('storage container legal-hold set --account-name {} -c {} -g {} --tags tag1 tag2 --w-all'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck("tags", ['tag1', 'tag2']),
JMESPathCheck("allowProtectedAppendWritesAll", True)
])

self.cmd('storage container legal-hold clear --account-name {} -c {} -g {} --tags tag1 tag2'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck("tags", []),
JMESPathCheck("allowProtectedAppendWritesAll", None)
])

self.cmd('storage container legal-hold set --account-name {} -c {} -g {} --tags tag3 tag4 --w-all false'.format(
storage_account, container_name, resource_group), checks=[
JMESPathCheck("tags", ['tag3', 'tag4']),
JMESPathCheck("allowProtectedAppendWritesAll", False)
])

0 comments on commit 26aa3f1

Please sign in to comment.