Repository navigation
feat(acl): convert ACL VHDs to COSI and publish to PMC - #8848
Adams Anaglo (adamsanaglo) wants to merge 82 commits into
Conversation
Add a post-build pipeline step that converts AzureContainerLinux (ACL) VHD images to COSI format using ImageCustomizer's convert command (v1.2.0-2). Changes: - New script: convert-vhd-to-cosi.sh downloads ACL VHD from blob storage, runs ImageCustomizer convert (VHD -> COSI), and uploads the COSI file - New make target: convert-vhd-to-cosi in packer.mk - Pipeline vars: set IMG_CUSTOMIZER_CONTAINER and IMG_CUSTOMIZER_VERSION on ACL build jobs in both PR and release pipelines - Builder template: add Convert ACL VHD to COSI step after SIG->classic conversion, conditioned on OS_SKU=AzureContainerLinux - Immutable copy: updated to also copy/remove .cosi files for ACL builds Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move the ACL COSI 'az storage blob copy start' from the VHD immutable copy task into a dedicated task with its own condition gating on OS_SKU=AzureContainerLinux, rather than using a bash if-block inside the VHD copy step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move 'Convert ACL VHD to COSI' task to after 'Copy to Immutable Storage Container' so the VHD is safely in immutable storage before conversion. Revert echo to original 'Successfully copied to immutable container'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move 'Copy to Immutable Storage Container' (VHD) after 'Copy COSI to Immutable Storage Container'. New order: 1. Convert ACL VHD to COSI 2. Copy COSI to Immutable Storage Container 3. Copy to Immutable Storage Container (VHD) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use less specific version tag to pick up patch releases automatically. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SIG_SOURCE_GALLERY_UNIQUE_NAME, SIG_SOURCE_IMAGE_NAME, and SIG_SOURCE_IMAGE_VERSION are set but never consumed by any script or template in the repo. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
FIPS jobs were missing IMG_CUSTOMIZER_CONTAINER and IMG_CUSTOMIZER_VERSION, which are required for the COSI conversion step. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Create generate-cosi-publishing-info.sh that writes cosi-publishing-info.json
with the immutable storage URL and image metadata, paralleling
generate-vhd-publishing-info.sh for VHDs.
Pipeline steps added after 'Copy COSI to Immutable Storage Container':
- Generate COSI Publishing Info (bash, no az-login needed)
- Publish COSI Publishing Info (artifact: cosi-publishing-info-{artifactName})
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generate cosi-publishing-info.json (with sha256, size, cosi_url, and image metadata) directly in convert-vhd-to-cosi.sh while the COSI artifact is still on disk. Remove the standalone script and make target. Pipeline order: Convert + generate info -> Publish artifact -> Copy to immutable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OS_NAME was referenced in cosi-publishing-info.json but never set. ACL COSI artifacts are always Linux, so hardcode it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The container entrypoint is already 'imagecustomizer', so the first argument should be the subcommand 'convert' directly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add an opt-in fallback to convert-vhd-to-cosi.sh so that when the MCR ImageCustomizer image for the requested version is unavailable, the script can pull from a configurable fallback registry (IMG_CUSTOMIZER_CONTAINER_FALLBACK, e.g. ghcr.io/microsoft/imagecustomizer). The fallback is gated by a script param defaulting to off, normalizes major.minor versions to major.minor.0 for GHCR's full semver tags, and is enabled for the ACL COSI conversion step. Bump IMG_CUSTOMIZER_VERSION from 1.4 to 1.5 for ACL jobs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Include the tag directly in IMG_CUSTOMIZER_CONTAINER (mcr.microsoft.com/azurelinux/imagecustomizer:1.5) and IMG_CUSTOMIZER_CONTAINER_FALLBACK (ghcr.io/microsoft/imagecustomizer:1.5.0) for the ACL COSI conversion. Drop the separate IMG_CUSTOMIZER_VERSION variable and the major.minor to major.minor.0 normalization from convert-vhd-to-cosi.sh, since both registries now carry fully-qualified tags. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nnection - convert-vhd-to-cosi.sh: convert+stage only; emit AFD cosi_url + sha1 for aks-rp cosi register - upload-cosi-to-pmc.sh (new) + make target: upload staged COSI under PMC's SC (no az-login dep) - pipeline: Upload COSI to PMC task under COSI_PMC_SERVICE_CONNECTION; drop obsolete COSI immutable-copy step - fix azExitCode masking + set -u guards
…y ADO compile-time authz
…copy azcopy cannot infer the service type for a custom *.azurefd.net host and won't send the AAD token to a non-default trusted suffix. Mint a storage-scoped token and PUT the staged .cosi directly to PMC's AFD upload endpoint (AFD passthrough-forwards the Bearer token to the blob origin), matching the proven Nebraska POC. Stream with curl -T to avoid loading a multi-GB COSI into memory.
A single Blob REST PUT is capped at 5000 MiB, so the real converted COSI (>5 GiB) failed with HTTP 413 RequestBodyTooLarge. Replace the curl PUT with a small cmd/cosi-upload Go tool that uses the Azure Blob SDK's UploadFile (16 MiB blocks, concurrency 8), which chunks via Put Block + Put Block List and is not subject to the single-PUT limit. Auth is unchanged (Microsoft Entra via the AzureCLI@2 login); AFD forwards the bearer token to the blob origin. azblob/azidentity are pinned to the versions already used by the e2e module.
There was a problem hiding this comment.
Pull request overview
This PR extends the Azure Container Linux (ACL) VHD → COSI flow by adding a dedicated upload path to PMC via an Azure Front Door (AFD) upload endpoint, wiring the new conversion + upload steps into the VHD builder release pipeline, and introducing a small Go-based uploader that uses Azure Blob SDK block uploads to avoid single-request size limits.
Changes:
- Add an ImageCustomizer-based VHD→COSI conversion script that stages the artifact and emits
cosi-publishing-info.json(includingsha1). - Add a Go CLI (
cmd/cosi-upload) plus a wrapper script to upload large COSI artifacts via chunked block-blob upload using Azure CLI (Entra ID) auth. - Wire new “Convert ACL VHD to COSI” and “Upload COSI to PMC” steps into the builder release template and set ImageCustomizer image variables in pipeline definitions.
Reviewed changes
Copilot reviewed 8 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
vhdbuilder/packer/imagecustomizer/scripts/convert-vhd-to-cosi.sh |
Downloads the staged ACL VHD, converts it to COSI via ImageCustomizer, stages the COSI, and generates cosi-publishing-info.json with hashes. |
vhdbuilder/packer/imagecustomizer/scripts/upload-cosi-to-pmc.sh |
Validates expected environment + artifacts and invokes the Go uploader to push to the PMC AFD upload endpoint. |
cmd/cosi-upload/main.go |
Implements block/chunked upload using azblob with Azure CLI credential for pipeline auth. |
packer.mk |
Adds convert-vhd-to-cosi, build-cosi-upload, and upload-cosi-to-pmc targets used by pipeline steps. |
.pipelines/templates/.builder-release-template.yaml |
Adds pipeline tasks to convert ACL VHD to COSI and upload COSI to PMC, and publishes cosi-publishing-info.json. |
.pipelines/.vsts-vhd-builder.yaml |
Sets ImageCustomizer container image variables (primary + GHCR fallback) for relevant stages. |
.pipelines/.vsts-vhd-builder-release.yaml |
Sets ImageCustomizer container image variables (primary + GHCR fallback) for relevant stages. |
go.mod |
Adds Azure Blob SDK + Azure Identity dependencies required by the new uploader tool. |
go.sum |
Updates module checksums for newly introduced Go dependencies. |
AgentBaker Linux gate detectiveRun: https://msazure.visualstudio.com/CloudNativeCompute/_build/results?buildId=182176653 Detective summary: Test/Scan completed the VHD scan, then the CIS baseline comparison again found Ubuntu 22.04 FIPS rules Likely cause / signature: $sig. Confidence: High for recurring baseline drift. Recommended owner/action: VHD validation owners should work repair #39802117 to reconcile the CIS baseline or fix image content. Strongest alternative: PR-specific component changes caused the CIS diff; less likely because the same two CIS rules reproduced across unrelated PRs and manual branches. Evidence: Timeline marks Test/Scan as succeededWithIssues; task log reports Wiki signature: $sig; repair #39802117 |
Integrate upstream/user/bfjelds/acl-cosi-simplified with the resolved COSI pipeline, validation, and ACL agent changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Retain the production ACG default and current image pins while preserving selectable Marketplace and development images. Integrate upstream ACL CVM support and align COSI scenarios with the updated compute SDK. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
1 open finding
1 resolved since last review
🧠 Review effort: Lite
There was a problem hiding this comment.
🔵 Needs a closer look
Marketplace metadata and COSI acceptance checks need correction, and the cross-architecture publishing path warrants final human review.
5 open findings
Marketplace ACL metadata is missing from VHD publishing info · New COSI validation accepts malformed partition layouts · New Conflicting duplicate image hashes are silently overwritten · New COSI validation does not verify artifact architecture · New IfE2E_GO_TEST_TIMEOUTis unset/empty, this produces-timeout \"\"whichgo testtreats as an…
🧠 Review effort: Lite
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
An unsupported Azure CLI option blocks COSI handoff, and the content validator accepts incomplete compression metadata.
9 open findings
Blob existence check uses unsupported --blob-url option · New Validator accepts unsupported disk LBA sizes · New COSI validation accepts invalid disk LBA sizes · New Compression validation does not require maxWindowLog · New COSI validation does not verify artifact architecture Conflicting duplicate image hashes are silently overwritten COSI validation accepts malformed partition layouts Marketplace ACL metadata is missing from VHD publishing info IfE2E_GO_TEST_TIMEOUTis unset/empty, this produces-timeout \"\"whichgo testtreats as an…
🧠 Review effort: Lite
| source_exists="$(az storage blob exists --blob-url "$source_uri" --auth-mode login \ | ||
| --query exists --output tsv)" |
Upstream main builds ACL only from the production Azure Compute Gallery. Keep acg (default) and acldevel, both gallery sources, and restore main's gallery-only Packer source wiring. This also resolves the review comment about empty base image fields in vhd-publishing-info.json for marketplace builds. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Concurrent COSI scenarios can fill the E2E runner’s temp volume with multiple multi-GB downloads.
9 open findings
Concurrent COSI downloads can exhaust runner disk and network capacity · New Blob existence check uses unsupported --blob-url option Compression validation does not require maxWindowLog COSI validation accepts invalid disk LBA sizes Validator accepts unsupported disk LBA sizes COSI validation does not verify artifact architecture Conflicting duplicate image hashes are silently overwritten COSI validation accepts malformed partition layouts IfE2E_GO_TEST_TIMEOUTis unset/empty, this produces-timeout \"\"whichgo testtreats as an…
1 resolved since last review
🧠 Review effort: Lite
Per Brian, drop the ACL_COSIContent_* scenarios. ACL may not move from 3.0 to 3.1, the 3.1 partition checks were never finished, and the layout may gain partitions. The COSI update scenarios now cover the contents: Trident verifies the metadata SHA-384 while staging. Keep the download and URL-sanitizing helpers the update scenarios use, in cosi_download.go, and remove the LocalValidator hook that only these scenarios needed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved script and Packer compatibility issues block approval.
4 open findings
7 resolved since last review
Concurrent COSI downloads can exhaust runner disk and network capacity Compression validation does not require maxWindowLog COSI validation accepts invalid disk LBA sizes Validator accepts unsupported disk LBA sizes COSI validation does not verify artifact architecture Conflicting duplicate image hashes are silently overwritten COSI validation accepts malformed partition layouts
🧠 Review effort: Lite
| vhd_name="${CAPTURED_SIG_VERSION}.vhd" | ||
| source_uri="${DESTINATION_STORAGE_CONTAINER}/${vhd_name}" |
| # Configuration for the Trident ACL Agent, which drives A/B OS updates on | ||
| # ACL nodes. The agent itself and its unit come from the trident-acl-agent | ||
| # package in the ACL base image; only its configuration and its start | ||
| # trigger are supplied here. | ||
| # cpAndMode creates the .service.d directory, so no mkdir is needed. | ||
| TAA_OVERRIDE_SRC=/home/packer/trident-acl-agent-override.conf | ||
| TAA_OVERRIDE_DEST=/etc/systemd/system/trident-acl-agent.service.d/10-aks.conf | ||
| cpAndMode $TAA_OVERRIDE_SRC $TAA_OVERRIDE_DEST 600 | ||
|
|
||
| TAA_PATH_UNIT_SRC=/home/packer/trident-acl-agent.path | ||
| TAA_PATH_UNIT_DEST=/etc/systemd/system/trident-acl-agent.path | ||
| cpAndMode $TAA_PATH_UNIT_SRC $TAA_PATH_UNIT_DEST 600 |
| return nil | ||
| } | ||
|
|
||
| func stageCOSIUpdateImage(ctx context.Context, sourceURL string, upload func(context.Context, string, *os.File) (string, error)) (string, string, error) { |
There was a problem hiding this comment.
in my previous testing, i modified the test framework's firewall to allow the POC nebraska server instead of this download-upload-download cycle ... when i try this approach, i see timeouts in the test.
There was a problem hiding this comment.
in case it is helpful, here are the firewall changes (i think) that i was using:
The Nebraska firewall change is entirely in two files, introduced by cf69abae3e : 45 added lines, 8 removed. It has two parts—allowing the endpoint and ensuring existing shared firewalls receive the rule.
1. Define and install the Nebraska allow rule
In e2e/scenario/aks_model.go :
Declare the endpoint:
const nebraskaPocFqdn = "nebraska-poc-download-ep-hjf7e5fseafnejha.b01.azurefd.net"
Create an application rule permitting HTTPS on port 443:
nebraskaPocRule := armnetwork.AzureFirewallApplicationRule{
Name: to.Ptr("nebraska-poc-fqdn"),
SourceAddresses: []*string{to.Ptr("*")},
Protocols: []*armnetwork.AzureFirewallApplicationRuleProtocol{
{
ProtocolType: to.Ptr(armnetwork.AzureFirewallApplicationRuleProtocolTypeHTTPS),
Port: to.Ptr[int32](443),
},
},
TargetFqdns: []*string{to.Ptr(nebraskaPocFqdn)},
}
Append it to the existing Allow application-rule collection:
Rules: []*armnetwork.AzureFirewallApplicationRule{
&aksAppRule,
&blobStorageAppRule,
&mooncakeMARRule,
&dmcRule,
&nebraskaPocRule,
},
This modifies the shared E2E firewall, not just the COSI scenario’s VM.
2. Detect existing firewalls missing the rule
In e2e/scenario/shared_infra.go:491–533 , firewallAppRulesUpToDate() previously checked only the test-storage hostname. The change requires both rules:
expectedStaticRuleFqdns := map[string]string{
"blob-storage-fqdn": config.Config.BlobStorageAccount() + ".blob.core.windows.net",
"nebraska-poc-fqdn": "nebraska-poc-download-ep-hjf7e5fseafnejha.b01.azurefd.net",
}
It scans the existing application rules, records their target FQDNs, and returns false if either expected rule is missing or points elsewhere:
for name, expectedFqdn := range expectedStaticRuleFqdns {
if foundRuleFqdns[name] != expectedFqdn {
return false
}
}
return true
Why this second part matters: the firewall persists between runs. Adding a rule to its creation model alone wouldn’t update an already-existing firewall.
The pre-existing ensureSharedFirewall() reconciliation code then updates a stale firewall in place, preserving its public IP and waiting for completion. No new reconciliation machinery, YAML variable, or manual firewall change was needed.
| - bash: echo '##vso[task.setvariable variable=ACL_BASE_IMAGE_SIGNED]true' | ||
| displayName: Set ACL_BASE_IMAGE_SIGNED (default signed) | ||
|
|
||
| - ${{ if eq(parameters.aclBaseImageSource, 'acldevel') }}: |
There was a problem hiding this comment.
I'd just use an if/else here rather than having a needless step in the acldevel case
| default: mcr.microsoft.com/azurelinux/imagecustomizer:1.5@sha256:b1a46447f67d73dce1a76163d3f8dbcbe12988b65c51393621529c52af160729 | ||
| - name: imgCustomizerContainerFallback | ||
| type: string | ||
| default: ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a |
There was a problem hiding this comment.
why do we need the fallback? using not using mcr.microsoft.com will most likely raise supply chain analysis warnings
There was a problem hiding this comment.
agree. in early development it was because imagecustomizer azurelinux release was lagging, but at this point we probably don't need it.
| # directly by e2e-template.yaml (which only needs the signed flag, not the | ||
| # variant-specific image-pinning vars). | ||
| parameters: | ||
| - name: aclBaseImageSource |
There was a problem hiding this comment.
would prefer if we align all parameter/variable naming style to CAPITAL_SNAKE_CASE
| // images. Authentication is Microsoft Entra ID via the Azure CLI login supplied | ||
| // by the pipeline's AzureCLI@2 task; the AFD endpoint transparently forwards the | ||
| // bearer token to the blob origin. | ||
| package main |
There was a problem hiding this comment.
let's create a separate go module
| - group: "AKS Node SIG UA Token (KV)" | ||
| - group: build_performance | ||
| - group: aks-vuln-to-kusto-tme | ||
| - group: "AKS Node SIG - PMC COSI TME" |
There was a problem hiding this comment.
this YAML Is sourced by pipelines running in other tenants, not just TME - would prefer we attach at this. via the ADO library rather than directly through YAML
| echo '##vso[task.setvariable variable=IMG_CUSTOMIZER_CONTAINER_FALLBACK]ghcr.io/microsoft/imagecustomizer:1.5.0@sha256:c74e16c636e08486f517cd3b446e3577e52b24feea06aa245a8742a9f1c4a70a' | ||
| displayName: Setup Build Variables | ||
| - template: ./.builder-cosi-convert-template.yaml |
There was a problem hiding this comment.
do we plan on using this template elsewhere? if not it would be simpler to just include that template's content directly within this template
| # via `command -v` guards, so it's a no-op on any tool that's already present. | ||
| - bash: | | ||
| set -euo pipefail | ||
| echo "==== Bootstrapping tooling on $(uname -a) ====" |
There was a problem hiding this comment.
this is a large script, would prefer we factor it out to a dedicated bash file somewhere under vhdbuilder - that way we can also write shellspec tests against it as well
Isolate the COSI uploader module, simplify conversion templates, extract and test tooling bootstrap, remove the non-MCR fallback, and align pipeline parameter and variable names. Keep tenant-specific PMC settings out of shared YAML and preserve unsigned ACL test/scan support. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved critical Azure CLI bootstrap and additional pipeline, version propagation, and COSI execution issues block approval.
5 open issues
Configure Azure CLI repository before ARM64 installation · New ACL CVM build copies missing override files Exporter uses wrong non-staging blob container Blob existence check uses unsupported --blob-url option IfE2E_GO_TEST_TIMEOUTis unset/empty, this produces-timeout \"\"whichgo testtreats as an…
Review effort: Lite
| tdnf|dnf) | ||
| pkg_install azure-cli | ||
| ;; |
| # node was provisioned from and is not replaced by an A/B update of /usr. | ||
| if isACL "$OS" "$OS_VARIANT"; then | ||
| if [ -z "${IMAGE_VERSION:-}" ]; then | ||
| IMAGE_VERSION=$(date +%Y%m.%d.0) |
There was a problem hiding this comment.
I dont think we should create an image version to set if packer doesn't pass in $IMAGE_VERSION, we should probably just fail
| fi | ||
| IMAGE_VERSION_FILEPATH=/opt/azure/containers/image-version | ||
| mkdir -p "$(dirname ${IMAGE_VERSION_FILEPATH})" | ||
| echo "IMAGE_VERSION=${IMAGE_VERSION}" > ${IMAGE_VERSION_FILEPATH} |
There was a problem hiding this comment.
does this IMAGE_VERSION_FILEPATH file need to be updated or does trident agent know to switch its context to the new partition's image and corresponding image version file path?
There was a problem hiding this comment.
like is there a mechanism on trident side that knows it has to change to the new image version? the comments for this file suggest this file DOES NOT change during update
context: Line 120: This file lives on the root partition, so it describes the node image the node was provisioned from and is not replaced by an A/B update of /usr.
There was a problem hiding this comment.
this is a complicated question. the problem is that ACL creates an updatable partition /usr and AgentBaker cannot currently change that parition because it cannot reseal/resign /usr and the ukis. that is why the comment is left here.
in the future, the AKS ACL image build will need to be changed to make everything updatable that the current build puts on /opt and /etc. but that hasn't been invented yet.
| METADATA_SHA384=$(tar -xOf "$STAGED_COSI" --occurrence=1 metadata.json | openssl dgst -sha384 -binary | base64) | ||
|
|
||
| # Normalize only the Nebraska-facing version; IMAGE_VERSION itself is unchanged. | ||
| if ! COSI_IMAGE_VERSION="$(generate_cosi_package_version "${IMAGE_VERSION}" "${ENABLE_FIPS:-false}")"; then |
There was a problem hiding this comment.
Potential current/target version mismatch: this normalizes the published version (for example, 202608.06.0 -> 202608.6.0, adding -fips when enabled), but post-install-dependencies.sh writes the raw IMAGE_VERSION into /opt/azure/containers/image-version, which the agent override configures as its current-version source.
The Trident agent source currently reads that file value directly (version.rs:90-97) and uses literal string equality against request.target_version for its AlreadyAtTarget checks (stage, finalize). If requests carry the normalized published version, the same image can compare unequal and miss the already-at-target short-circuit.
Could we ensure the agent-readable stamp and requested target use the same canonical version, including FIPS handling, and add a regression case with a zero-padded component? Please also confirm the agent revision shipped in these images uses the linked comparison path; the exact runtime impact depends on that revision and the targetVersion supplied by RP.
There was a problem hiding this comment.
agree that we need to trace through aks-rp abupdate annotations and image-version file to ensure that these are all consistent!
trident-acl-agent should not understand fips vs not-fips (and the AlreadyAtTarget is really just a nice-to-have, as aks-rp should know what the node is), it really just passes the aks-rp request along to Nebraska.
There was a problem hiding this comment.
until the image version is updatable, it might be better to use this in parts/linux/cloud-init/artifacts/acl/trident-acl-agent-override.conf:
TRIDENT_ACL_AGENT_CURRENT_VERSION_PATH=/dev/null
TRIDENT_ACL_AGENT_CURRENT_VERSION_FALLBACK=0.0.0


What this PR does / why we need it:
Builds on the ACL VHD->COSI conversion work to upload the COSI directly to PMC via its AFD upload endpoint under PMC's service connection.
upload-cosi-to-pmc.shand packer.mk targets.cosi-publishing-info.jsonuses the AFD download URL and addssha1.202608.06.0->202608.6.0) and appends-fipsfor FIPS builds (driven byENABLE_FIPS, never duplicated), writing the result tocosi-publishing-info.json.image_version.IMAGE_VERSIONand SIG versions are unchanged. COSI blob names include the SKU to keep architecture/FIPS variants distinct. Covered by ShellSpec tests.azblobandazidentitydependencies.Which issue(s) this PR fixes:
Fixes #