Conversation
|
Validation for Breaking Change Starting...
Thanks for your contribution! |
|
Hi @nguyenm2151, |
|
Thank you for your contribution! We will review the pull request and get back to you soon. |
|
The git hooks are available for azure-cli and azure-cli-extensions repos. They could help you run required checks before creating the PR. Please sync the latest code with latest dev branch (for azure-cli) or main branch (for azure-cli-extensions). pip install azdev --upgrade
azdev setup -c <your azure-cli repo path> -r <your azure-cli-extensions repo path>
|
There was a problem hiding this comment.
Pull request overview
This PR updates the acrtransfer Azure CLI extension to support new ACR import/export pipeline storage authentication modes by moving to a newer vendored Container Registry management SDK and surfacing validation, guidance, and scenario coverage for the new flags.
Changes:
- Vendor and switch to Container Registry management SDK
2025-06-01-previewfor pipeline operations. - Add
--storage-access-modevalidation (Entra managed identity vs SAS token) and update pipeline create logic accordingly. - Add scenario tests covering both auth modes and identity behaviors.
Reviewed changes
Copilot reviewed 40 out of 63 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/acrtransfer/azext_acrtransfer/vendored_sdks/containerregistry/v2025_06_01_preview/** |
Adds new vendored SDK version used by the extension for pipeline operations. |
src/acrtransfer/azext_acrtransfer/commands.py |
Points command groups at the new SDK operation templates and min API. |
src/acrtransfer/azext_acrtransfer/_params.py |
Introduces --storage-access-mode and updates help text/argument wiring. |
src/acrtransfer/azext_acrtransfer/_validators.py |
Adds validation/translation rules for storage access mode vs secret URI. |
src/acrtransfer/azext_acrtransfer/exportpipeline.py |
Updates export pipeline creation to set storage_access_mode and prints permission guidance. |
src/acrtransfer/azext_acrtransfer/importpipeline.py |
Updates import pipeline creation to set storage_access_mode and prints permission guidance. |
src/acrtransfer/azext_acrtransfer/pipelinerun.py |
Adds diagnostic logging about which storage auth method is being used. |
src/acrtransfer/azext_acrtransfer/utility_functions.py |
Switches model imports to the new SDK version. |
src/acrtransfer/azext_acrtransfer/tests/latest/test_acrtransfer_pipelines.py |
Adds scenario tests for import/export pipelines across auth/identity modes. |
| def _extract_storage_account_resource_id(subscription_id, resource_group_name, container_uri): | ||
| """Extract storage account resource ID from container URI. | ||
| Expected format: https://<storage-account-name>.blob.core.windows.net/<container-name> | ||
| """ | ||
| try: | ||
| # Parse the URI to get storage account name | ||
| from urllib.parse import urlparse | ||
| parsed = urlparse(container_uri) | ||
| storage_account_name = parsed.hostname.split('.')[0] | ||
| return f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Storage/storageAccounts/{storage_account_name}" | ||
| except Exception: | ||
| return "<storage-account-resource-id>" | ||
|
|
||
|
|
||
| def _extract_keyvault_resource_id(subscription_id, resource_group_name, keyvault_secret_uri): | ||
| """Extract key vault resource ID from secret URI. | ||
| Expected format: https://<keyvault-name>.vault.azure.net/secrets/<secret-name> | ||
| """ | ||
| try: | ||
| from urllib.parse import urlparse | ||
| parsed = urlparse(keyvault_secret_uri) | ||
| keyvault_name = parsed.hostname.split('.')[0] | ||
| return f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.KeyVault/vaults/{keyvault_name}" | ||
| except Exception: | ||
| return "<key-vault-resource-id>" | ||
|
|
There was a problem hiding this comment.
The new helper functions for extracting resource IDs and printing permission guidance are duplicated in both importpipeline.py and exportpipeline.py. To reduce drift and simplify future changes, consider moving these helpers into utility_functions.py (or a dedicated module) and reuse them from both pipeline implementations.
| logger.warning("Please run:") | ||
| logger.warning(" az role assignment create --assignee \"%s\" --role \"Key Vault Secrets User\" --scope \"%s\"", principal_id, keyvault_resource_id) |
There was a problem hiding this comment.
The Key Vault guidance always suggests an RBAC role assignment (Key Vault Secrets User). If the vault is configured with access policies (RBAC disabled), this instruction won’t work and users will need a KV access policy instead. Please adjust the guidance (or detect/mention the KV authorization mode) so the instructions are actionable in both configurations.
| logger.warning("Please run:") | |
| logger.warning(" az role assignment create --assignee \"%s\" --role \"Key Vault Secrets User\" --scope \"%s\"", principal_id, keyvault_resource_id) | |
| logger.warning("If your Key Vault uses Azure RBAC for access control, you can run:") | |
| logger.warning(" az role assignment create --assignee \"%s\" --role \"Key Vault Secrets User\" --scope \"%s\"", principal_id, keyvault_resource_id) | |
| logger.warning("If your Key Vault uses access policies (RBAC disabled), configure a Key Vault access policy granting this managed identity at least 'get' permission on secrets.") |
| def _extract_storage_account_resource_id(subscription_id, resource_group_name, container_uri): | ||
| """Extract storage account resource ID from container URI. | ||
| Expected format: https://<storage-account-name>.blob.core.windows.net/<container-name> | ||
| """ | ||
| try: | ||
| # Parse the URI to get storage account name | ||
| from urllib.parse import urlparse | ||
| parsed = urlparse(container_uri) | ||
| storage_account_name = parsed.hostname.split('.')[0] | ||
| return f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.Storage/storageAccounts/{storage_account_name}" | ||
| except Exception: | ||
| return "<storage-account-resource-id>" | ||
|
|
||
|
|
||
| def _extract_keyvault_resource_id(subscription_id, resource_group_name, keyvault_secret_uri): | ||
| """Extract key vault resource ID from secret URI. | ||
| Expected format: https://<keyvault-name>.vault.azure.net/secrets/<secret-name> | ||
| """ | ||
| try: | ||
| from urllib.parse import urlparse | ||
| parsed = urlparse(keyvault_secret_uri) | ||
| keyvault_name = parsed.hostname.split('.')[0] | ||
| return f"/subscriptions/{subscription_id}/resourceGroups/{resource_group_name}/providers/Microsoft.KeyVault/vaults/{keyvault_name}" | ||
| except Exception: | ||
| return "<key-vault-resource-id>" | ||
|
|
There was a problem hiding this comment.
The new helper functions for extracting resource IDs and printing permission guidance are duplicated in both exportpipeline.py and importpipeline.py. Consider consolidating them in a shared helper module to avoid divergent behavior and repeated maintenance.
| logger.warning("Please run:") | ||
| logger.warning(" az role assignment create --assignee \"%s\" --role \"Key Vault Secrets User\" --scope \"%s\"", principal_id, keyvault_resource_id) | ||
| logger.warning("Note: If the Key Vault is in a different resource group, update the --scope parameter accordingly.") |
There was a problem hiding this comment.
The Key Vault permission guidance always recommends an RBAC role assignment. For vaults using access policies (RBAC disabled), the recommended command won’t grant secret access. Consider updating the guidance to mention/access-policy steps or to detect and tailor instructions to the vault’s authorization mode.
| logger.warning("Please run:") | |
| logger.warning(" az role assignment create --assignee \"%s\" --role \"Key Vault Secrets User\" --scope \"%s\"", principal_id, keyvault_resource_id) | |
| logger.warning("Note: If the Key Vault is in a different resource group, update the --scope parameter accordingly.") | |
| logger.warning("If your Key Vault is configured to use Azure role-based access control (RBAC), you can grant access by running:") | |
| logger.warning(" az role assignment create --assignee \"%s\" --role \"Key Vault Secrets User\" --scope \"%s\"", principal_id, keyvault_resource_id) | |
| logger.warning("Note: If the Key Vault is in a different resource group, update the --scope parameter accordingly.") | |
| logger.warning("If your Key Vault is configured to use access policies (RBAC disabled), instead configure an access policy for this managed identity, for example:") | |
| logger.warning(" az keyvault set-policy --name <key-vault-name> --object-id \"%s\" --secret-permissions get list", principal_id) | |
| logger.warning("Ensure that you use the correct Key Vault name and any additional secret permissions required by your scenario.") |
| logger.warning("Authenticating to Storage Account using Storage SAS Token.") | ||
| except Exception: | ||
| pass |
There was a problem hiding this comment.
get_pipelinerun swallows all exceptions when trying to resolve the pipeline and determine storage_access_mode. A bare except Exception: pass makes diagnosing real failures difficult and can hide regressions. Please catch the specific exceptions you expect (e.g., HttpResponseError/ResourceNotFoundError) and log at debug level when the lookup fails.
| # Generate SAS token and store in Key Vault | ||
| from datetime import datetime, timedelta | ||
| expiry = (datetime.utcnow() + timedelta(days=30)).strftime('%Y-%m-%dT%H:%MZ') | ||
| sas_token = self.cmd('storage container generate-sas ' | ||
| '--account-name {storage_account} ' | ||
| '--name {container} ' | ||
| '--permissions racwdl ' | ||
| f'--expiry {expiry} ' | ||
| '-o tsv').output.strip() |
There was a problem hiding this comment.
The test generates a container SAS token before the blob container is created. az storage container generate-sas typically expects the container to exist (or will fail depending on auth mode). Create the storage container before generating/storing the SAS token (or generate an account SAS instead).
| # Generate SAS token and store in Key Vault | ||
| from datetime import datetime, timedelta | ||
| expiry = (datetime.utcnow() + timedelta(days=30)).strftime('%Y-%m-%dT%H:%MZ') | ||
| sas_token = self.cmd('storage container generate-sas ' | ||
| '--account-name {storage_account} ' | ||
| '--name {container} ' | ||
| '--permissions racwdl ' | ||
| f'--expiry {expiry} ' | ||
| '-o tsv').output.strip() | ||
|
|
There was a problem hiding this comment.
Same issue as the export SAS test: the SAS token is generated before the storage container is created. Move the container creation ahead of storage container generate-sas (or switch to an account SAS) to avoid intermittent failures.
| c.argument('storage_account_container_uri', options_list=['--storage-container-uri', '-c'], validator=validate_storage_account_container_uri, help='Storage account container URI of the source or target storage account container of the form https://$MyStorageAccount.blob.core.windows.net/$MyContainer. Note that the URI may be different outside of AzureCloud.') | ||
| c.argument('keyvault_secret_uri', options_list=['--secret-uri', '-s'], validator=validate_keyvault_secret_uri, help='Keyvault secret URI containing a valid SAS token to the associated storage account of the form https://$MyKeyvault.vault.azure.net/secrets/$MySecret. Note that the URI may be different outside of AzureCloud.') | ||
| c.argument('user_assigned_identity_resource_id', options_list=['--assign-identity', '-i'], validator=validate_user_assigned_identity_resource_id, help='User assigned identity resource ID of the form /subscriptions/$MySubID/resourceGroups/$MyRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/$MyIdentity.') | ||
| c.argument('storage_access_mode', options_list=['--storage-access-mode', '-m'], validator=validate_storage_access_mode_and_secret_uri, help='Storage account access mode. Allowed values: entra-mi-auth, storage-sas-token. When using entra-mi-auth, a managed identity is required (use --assign-identity).') | ||
| c.argument('keyvault_secret_uri', options_list=['--secret-uri', '-s'], validator=validate_keyvault_secret_uri, help='Keyvault secret URI containing a valid SAS token to the associated storage account of the form https://$MyKeyvault.vault.azure.net/secrets/$MySecret. Note that the URI may be different outside of AzureCloud. Required when --storage-access-mode is storage-sas-token.') | ||
| c.argument('user_assigned_identity_resource_id', options_list=['--assign-identity', '-i'], validator=validate_user_assigned_identity_resource_id, help='Managed identity for the pipeline. Provide a user-assigned identity resource ID of the form /subscriptions/$MySubID/resourceGroups/$MyRG/providers/Microsoft.ManagedIdentity/userAssignedIdentities/$MyIdentity, or use [system] to provision a system-assigned identity. When using --storage-access-mode entra-mi-auth, if not specified or if [system] is used, a system-assigned managed identity will be automatically provisioned.') |
There was a problem hiding this comment.
storage_access_mode is added under the generic argument_context('acr'), which makes --storage-access-mode appear on all az acr ... commands (including unrelated ones). Consider scoping this argument (and its validator) to only the acr import-pipeline create / acr export-pipeline create command contexts to avoid polluting the broader ACR command surface.
|
Hi @nguyenm2151 Release SuggestionsModule: acrtransfer
Notes
|
This checklist is used to make sure that common guidelines for a pull request are followed.
Related command
General Guidelines
azdev style <YOUR_EXT>locally? (pip install azdevrequired)python scripts/ci/test_index.py -qlocally? (pip install wheel==0.30.0required)For new extensions:
About Extension Publish
There is a pipeline to automatically build, upload and publish extension wheels.
Once your pull request is merged into main branch, a new pull request will be created to update
src/index.jsonautomatically.You only need to update the version information in file setup.py and historical information in file HISTORY.rst in your PR but do not modify
src/index.json.