feat: update snapshot to be generic#8952
Conversation
There was a problem hiding this comment.
Pull request overview
This PR refactors the Ubuntu “snapshot update” mechanism into a generic node-side reconciliation loop (knead) and extracts the existing Ubuntu security patching behavior into a dedicated handler script, while ensuring both VHD-baked assets and hotfix delivery paths include the new script layout.
Changes:
- Refactors
ubuntu-snapshot-update.shinto a generic reconciler that reads desired state from a ConfigMap and reports convergence via node annotations. - Splits Ubuntu security patching into a new
security-update.shhandler, and wires it into VHD build + hotfix delivery. - Adds/updates VHD content tests and ShellSpec coverage for the new assets and contracts.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| vhdbuilder/packer/vhd-image-builder-cvm.json | Stages security-update.sh into the Ubuntu VHD build inputs. |
| vhdbuilder/packer/vhd-image-builder-base.json | Stages security-update.sh into the Ubuntu VHD build inputs. |
| vhdbuilder/packer/vhd-image-builder-arm64-gen2.json | Stages security-update.sh into the Ubuntu VHD build inputs. |
| vhdbuilder/packer/vhd-image-builder-arm64-gb.json | Stages security-update.sh into the Ubuntu VHD build inputs. |
| vhdbuilder/packer/test/linux-vhd-content-test.sh | Adds VHD validation for snapshot/security updater assets and permissions. |
| vhdbuilder/packer/packer_source.sh | Copies security-update.sh into /opt/azure/containers/ during image build. |
| spec/parts/linux/cloud-init/artifacts/ubuntu-snapshot-update_spec.sh | Updates ShellSpec to cover the new generic reconciler behavior. |
| spec/parts/linux/cloud-init/artifacts/snapshot-update-service_spec.sh | Adds ShellSpec assertions for packer inputs and hotfix transition behavior. |
| spec/parts/linux/cloud-init/artifacts/security-update_spec.sh | Adds ShellSpec coverage for security-update.sh handler logic. |
| pkg/agent/variables.go | Exposes securityUpdateScript as a cloud-init variable for hotfix delivery. |
| pkg/agent/const.go | Adds constant path for security-update.sh artifact lookup. |
| parts/linux/cloud-init/nodecustomdata.yml | Hotfix-delivers both reconciler and handler scripts together on Ubuntu. |
| parts/linux/cloud-init/artifacts/ubuntu/ubuntu-snapshot-update.sh | Implements generic reconciliation loop and sources the security handler. |
| parts/linux/cloud-init/artifacts/ubuntu/security-update.sh | New Ubuntu security patch handler (snapshot sources + unattended upgrades). |
| hotfix/hotfix_generate.py | Adds hotfix mapping for ubuntu/security-update.sh. |
| CODEOWNERS | Adds ownership entries for new/updated scripts and specs. |
| # shellcheck disable=SC2086 | ||
| if ! payload="$($KUBECTL get cm -n "${KNEAD_COMPONENT_CONFIG_NAMESPACE}" "${KNEAD_COMPONENT_CONFIGMAP}" -o "jsonpath={.data.${KNEAD_COMPONENT_CONFIG_KEY_JSONPATH}}")"; then | ||
| echo "failed to read live-patching-config ConfigMap" >&2 |
There was a problem hiding this comment.
I'm not sure what copilot is getting at here. Looks good from here
KUBECTL=kubectl
KNEAD_COMPONENT_CONFIG_NAMESPACE=kube-system
KNEAD_COMPONENT_CONFIGMAP=live-patching-config
KNEAD_COMPONENT_CONFIG_KEY_JSONPATH='live-patching-config.json'
SOURCED=1
bash -c '
source parts/linux/cloud-init/artifacts/ubuntu/ubuntu-snapshot-update.sh
knead_read_configmap
a39c2e4dd1f80854162ad925fdb6b74c4a7b415c5d75c8ee39094453e9df68b9 |
jq -e .
'
{
"components": [
{
"name": "securityPatch",
"nodeConfig": "{"agentPools":{"nodepool1":{"goldenTimestamp":"20260710T000000Z"}}}"
}
]
}
and just full output -
k get cm -n kube-system live-patching-config -ojson
{
"apiVersion": "v1",
"data": {
"live-patching-config.json": "{"components":[{"name":"securityPatch","nodeConfig":"{\"agentPools\":{\"nodepool1\":{\"goldenTimestamp\":\"20260710T000000Z\"}}}"}]}"
},
"kind": "ConfigMap",
"metadata": {
"creationTimestamp": "2026-07-15T05:51:14Z",
"name": "live-patching-config",
"namespace": "kube-system",
"resourceVersion": "90353",
"uid": "19ba9af1-da09-4504-9a34-67ad3b73259d"
}
}
| eval 'unattended-upgrade() { | ||
| TEST_UNATTENDED_ATTEMPT=$((TEST_UNATTENDED_ATTEMPT + 1)) | ||
| echo "unattended-upgrade called: ${TEST_UNATTENDED_ATTEMPT}" | ||
| [ "${TEST_UNATTENDED_ATTEMPT}" -ge 3 ] | ||
| }' | ||
| TEST_UNATTENDED_ATTEMPT=0 | ||
| export TEST_UNATTENDED_ATTEMPT |
There was a problem hiding this comment.
huh. So we have apt-get() in cse_helpers and systemd-sysext() in cse_install_spec
so this is following pattern of "-" for executable wrapping.
| eval 'unattended-upgrade() { | ||
| TEST_UNATTENDED_ATTEMPT=$((TEST_UNATTENDED_ATTEMPT + 1)) | ||
| echo "unattended-upgrade called: ${TEST_UNATTENDED_ATTEMPT}" | ||
| return 1 | ||
| }' | ||
| TEST_UNATTENDED_ATTEMPT=0 | ||
| export TEST_UNATTENDED_ATTEMPT |
| # shellcheck disable=SC1091 | ||
| source /opt/azure/containers/security-update.sh | ||
|
|
There was a problem hiding this comment.
This handler is intentionally a hard dependency. The VHD and hotfix paths deliver ubuntu-snapshot-update.sh and security-update.sh together, and snapshot-update-service_spec.sh validates both delivery paths. If the handler is missing, source already exits nonzero and reports the exact missing path, so an additional existence check would duplicate the same failure behavior without improving recovery.
What this PR does / why we need it:
updating ubuntu-snapshot-updater.sh to be generic and allow multiple component update through it in the future
splitting the original security updates into their own file to be called by ubuntu-snapshot-updater. So we can keep component specific needs separate.
why not a new service? dropping the old one would remove the ability to hotfix those on older vhd using legacy service. aks will support both update methods. same concerns with a new file called by the existing service.
if we hotfix an old vhd they'll get the new generic .sh along with the security updater. so this seems the safest route to old/new
Which issue(s) this PR fixes:
Fixes #