[Change Safety] Az.Cdn: opt-in + fix dynamicparam forwarding for custom-fronted profile cmdlets - #29999
[Change Safety] Az.Cdn: opt-in + fix dynamicparam forwarding for custom-fronted profile cmdlets#29999YangAn-microsoft wants to merge 12 commits into
Conversation
…ile cmdlets Regenerated Az.Cdn with the autorest.powershell generator fix (see Azure/autorest.powershell#1555): the outer proxy for custom-fronted write cmdlets (New/Remove/Update-Az{,FrontDoor}CdnProfile) now forwards the wrapped custom function's dynamicparam block, so -AcquirePolicyToken and -ChangeReference actually bind on these commands. Previously they were rejected with 'A parameter cannot be found that matches parameter name AcquirePolicyToken', even though the module was already opted into enable-change-safety. Live-validated: New-AzFrontDoorCdnProfile -AcquirePolicyToken now successfully creates a profile in a real subscription.
There was a problem hiding this comment.
Copilot wasn't able to review any files in this pull request.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
The manual regeneration used to apply the change-safety dynamicparam fix
pointed -ExamplesFolder at generated/Cdn/Cdn.Autorest/examples, which
does not exist (the real, hand-maintained example .md files live at
src/Cdn/Cdn.Autorest/examples). Export-ProxyCmdlet found no matching
example for any cmdlet and fell back to the '{{ Add code here }}'
placeholder in every exported cmdlet's comment-based help. Re-ran the
regeneration with the correct examples path to restore the real
examples; the change-safety dynamicparam fix is unaffected.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
The bare autorest run in the earlier regeneration (68ff900) reset src/Cdn/Cdn.Autorest/Properties/AssemblyInfo.cs's AssemblyVersion/ AssemblyFileVersion from the real released 6.0.2 to AutoRest's default placeholder 0.1.0.0. The generated/ copy (the one actually compiled into the shipped Az.Cdn.private.dll) was unaffected and still correctly says 6.0.2; this only restores the stray src/ copy so it doesn't poison a future regeneration cycle.
The regeneration used the local autorest.powershell generator (via a directory junction into the AutoRest extension cache) instead of the officially published @autorest/powershell@4.0.754 npm package that CI uses. The generator embeds its own source file path into a diagnostic comment (a limitation note about deserializeFromResponse and headers), so this comment picked up my local dev machine's absolute path instead of the original CI path. Purely cosmetic/non-functional; reverted to keep the diff free of unrelated, machine-specific noise.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
…dlet New-AzFrontDoorCdnRuleSet has no custom/*.ps1 front, so its dynamicparam forwarding was already working before this PR's fix (only the custom-fronted profile cmdlets were broken). Adding this scenario alongside the existing custom-fronted ones gives explicit regression coverage for both code paths going forward. Live-validated: created a real Front Door rule set end-to-end with -AcquirePolicyToken in the BAMI tenant.
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
|
Azure Pipelines: Successfully started running 1 pipeline(s). There may be pipelines that require an authorized user to comment /azp run to run. |
… cmdlets, not just profile cmdlets
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
…(matches Aks/ContainerRegistry/EventHub/ServiceBus)
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
…dn migration/route/sku cmdlets
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
…hive-bot regenerates separately)
|
|
|
/azp run |
|
Azure Pipelines: Successfully started running 3 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 17 out of 17 changed files in this pull request and generated no new comments.
Suppressed comments (16)
src/Cdn/LiveTests/Cdn.Autorest/TestLiveScenarios.ps1:44
- This scenario creates the prerequisite Front Door CDN profile without
-AcquirePolicyToken. If Change Safety is required in the test subscription, the profile creation can fail before the ruleset cmdlet under test is exercised.
New-AzFrontDoorCdnProfile -ResourceGroupName $rgName -Name $profileName -SkuName Standard_AzureFrontDoor -Location Global -Confirm:$false | Out-Null
src/Cdn/Cdn/ChangeLog.md:22
- The Upcoming Release entry is too vague and uses inconsistent terminology ("ChangeSafety" vs "Change Safety"). Changelog entries here should be user-facing and should mention what changed (e.g., which parameters/cmdlets are impacted).
* Added ChangeSafety Support
src/Cdn/LiveTests/Cdn.Autorest/TestLiveScenarios.ps1:31
- This scenario creates the prerequisite Front Door CDN profile without
-AcquirePolicyToken. If Change Safety is required in the test subscription, the profile creation can fail before the endpoint cmdlet under test is exercised.
This issue also appears on line 44 of the same file.
New-AzFrontDoorCdnProfile -ResourceGroupName $rgName -Name $profileName -SkuName Standard_AzureFrontDoor -Location Global -Confirm:$false | Out-Null
src/Cdn/Cdn.Autorest/custom/New-AzCdnProfile.ps1:159
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/New-AzFrontDoorCdnProfile.ps1:205
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Remove-AzFrontDoorCdnProfile.ps1:169
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Enable-AzFrontDoorCdnProfileMigration.ps1:133
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Invoke-AzCdnCommitProfileToAFDMigration.ps1:128
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/New-AzFrontDoorCdnRoute.ps1:383
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Update-AzFrontDoorCdnProfile.ps1:230
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Move-AzCdnProfileToAFD.ps1:220
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Remove-AzCdnProfile.ps1:168
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Test-AzFrontDoorCdnProfileMigration.ps1:127
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Update-AzFrontDoorCdnProfileSku.ps1:153
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Start-AzFrontDoorCdnProfilePrepareMigration.ps1:181
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
src/Cdn/Cdn.Autorest/custom/Update-AzCdnProfile.ps1:174
- The dynamicparam forwarding assumes
GetDynamicParameters()is non-null.IDynamicParameters.GetDynamicParameters()is allowed to return$null, which would throw during parameter binding. Capture the result and null-check before enumerating.
$instance = [System.Activator]::CreateInstance($wrapped.ImplementingType)
foreach ($entry in $instance.GetDynamicParameters().GetEnumerator()) {
Summary
Opts Az.Cdn into Change Safety (
-AcquirePolicyToken/-ChangeReference) and fixes a generator gap that prevented these parameters from binding on the custom-fronted CDN/AFD profile cmdlets.What's included
enable-change-safety: trueopt-in for the Cdn AutoRest project (profiles + AFD endpoints).dynamicparamblocks on the 6 custom-fronted profile cmdlets (New/Remove/Update-Az{,FrontDoor}CdnProfile) forwarding the wrapped private cmdlet's dynamic parameters.-AcquirePolicyTokenbefore ever dispatching to the custom function.src/Cdn/LiveTests/Cdn.Autorest/TestLiveScenarios.ps1) for both custom-fronted cmdlets (New-AzCdnProfile,New-AzFrontDoorCdnProfile) and generated, non-custom-fronted cmdlets (New-AzFrontDoorCdnEndpoint,New-AzFrontDoorCdnRuleSet), so a regression in either code path is caught going forward.Validation — live test evidence
Tested against a real Azure subscription (
f758ac53-3e63-4317-a956-0997793808d7, tenant4f00b3b6-2940-4f2c-b037-94637c180d30) — not mocked/recorded.New-AzFrontDoorCdnEndpoint -AcquirePolicyToken(generated cmdlet) — passed before this fix.New-AzFrontDoorCdnProfile -AcquirePolicyToken(custom-fronted cmdlet) — failed before this fix, passes after.Before the fix:
Confirmed root cause — the outer public proxy had no
dynamicparamblock at all:After the fix (rebuilt
Az.Cdn.private.dllwith the generator change, regeneratedexports/*.ps1):The profile was created successfully end-to-end, confirming
-AcquirePolicyTokennow binds and the change-safety token acquisition path executes.Deeper verification — proof the token acquisition pipeline actually fired
Binding the parameter and getting a successful create doesn't, by itself, prove ARM ever received the policy token — the write could succeed either way on a subscription that doesn't mandate it. For AutoRest-generated cmdlets this pipeline hook (
ContextAdapter.AddChangeSafetyPolicyTokenHandler) has no built-in-Debugobservability (unlike the SDK/RM cmdlet path), so a temporary, env-var-gated trace was added toPolicyTokenAcquirer.StampPolicyTokenAsync's call site (reverted afterward, verified via cleangit status) to get definitive proof:This confirms: the outgoing
PUTwas intercepted, a realPOSTto ARM'sacquirePolicyTokenendpoint was made and returned200 OK, the token was stamped onto the request as thex-ms-policy-external-evaluationsheader, and the subsequent async-pollGETwas correctly skipped by the write-verb gate.Additional live test — generated (non-custom-fronted) cmdlet
The scenarios above cover the two custom-fronted profile cmdlets. To also verify the code path that was never broken (plain generated cmdlets, which already had working dynamicparam forwarding before this PR), added and live-tested a new scenario against
New-AzFrontDoorCdnRuleSet— a fully generated cmdlet with nocustom/*.ps1front:This gives explicit regression coverage for both the custom-fronted path (fixed by this PR) and the plain-generated path (already correct), so a future change that breaks either one will be caught by
TestLiveScenarios.ps1.Generator fix: Azure/autorest.powershell#1555