Skip to content

fix(helpers): append vendor cloud-config lists instead of dropping them#6933

Open
goldberl wants to merge 1 commit into
canonical:mainfrom
goldberl:adjust-cloud-config-merge-strategy
Open

fix(helpers): append vendor cloud-config lists instead of dropping them#6933
goldberl wants to merge 1 commit into
canonical:mainfrom
goldberl:adjust-cloud-config-merge-strategy

Conversation

@goldberl

@goldberl goldberl commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Proposed Commit Message

fix(helpers): append vendor cloud-config lists instead of dropping them
    
When deploying a machine (such as a MAAS node) with a custom user-data
configuration, top-level lists in vendor-data (e.g., `write_files` or
`runcmd`) are silently ignored if the user-data contains the same keys.
This occurs because user-data is loaded first, and the default merging
strategy (`no_replace` with `recurse_list=False`) discards duplicate
keys from subsequent sources.
    
In MAAS deployments, this behavior breaks networking by completely
dropping the MAAS-generated `write_files` block that writes the netplan
configuration (e.g., `/etc/netplan/50-maas.yaml`), leaving only the
PXE interface configured.
    
This PR fixes  this by updating `ConfigMerger._get_instance_configs`
to inject a default `merge_how` strategy for `vendor_cloud_config`
and `vendor2_cloud_config`. This updates the strategy to:
`list(append)+dict(no_replace,recurse_list)+str()`
    
This ensures vendor-defined lists are appended to user-defined lists
rather than being discarded, while maintaining `no_replace` for
dictionary values and strings.
    
Signed-off-by: Leah Goldberg <leah.goldberg@canonical.com>

Fixes GH-6268
LP: #2158442

Additional Context

This PR fixes an issue where the default merge strategy for user-data and vendor-data silently discards critical vendor-data list keys (such as write_files). Because user-data is loaded first, cloud-init's default no_replace strategy drops matching vendor keys. This breaks networking on ephemeral platforms like MAAS by wiping out vendor generated netplan configurations.

This fix injects a default merge_how directive into vendor configs (vendor_cloud_config, vendor2_cloud_config) to ensure list values are appended instead of dropped: list(append)+dict(no_replace,recurse_list)+str()

For example:

user-data

{
    "write_files": [
        {"path": "/leah.txt", "content": "hello leah", "permissions": "0644"}
    ]
}

vendor-data


{
    "write_files": [
        {"path": "/etc/netplan/50-maas.yaml", "content": "network:\n...", "permissions": "0600"}
    ],
    "runcmd": ["netplan apply"]
}

The current way of merging is:

{
    "write_files": [
        {"path": "/leah.txt", "content": "hello leah", "permissions": "0644"}
    ],
    "runcmd": ["netplan apply"] 
}

Notice how the write_files from the vendor-data is ignored since the user-data already defined write_files.

This PR would change the merge to be:

{
    "write_files": [
        {"path": "/leah.txt", "content": "hello leah", "permissions": "0644"},
        {"path": "/etc/netplan/50-maas.yaml", "content": "network:\n...", "permissions": "0600"} 
    ],
    "runcmd": ["netplan apply"]
}

This ensures list values are appended instead of dropped.

Test Steps

How to reproduce the bug

  1. Deploy MAAS (3.4 or 3.6 - deb or snap) and add a VM.
  2. Customize the VM network so it's non-default (e.g. add a VLAN).
image
  1. Deploy the machine to memory with a custom cloud-init config:
#cloud-config

write_files:
  - path: /dave.txt
    content: "hello"
    owner: root:root
    permissions: '0644'
  1. Once the VM is deployed, note the network config. It will only have the PXE config and drop the custom config you added.

Actual behavior

Name State IPv4 IPv6 Type Snapshots
octopus-memory RUNNING 10.239.17.2 (enp5s0) VIRTUAL-MACHINE 0

The deployed machine only has the PXE interface (enp5s0) configured.

Expected behavior

Name State IPv4 IPv6 Type Snapshots
octopus-memory RUNNING 10.239.17.2 (enp5s0)
10.20.0.1 (enp5s0.100)
VIRTUAL-MACHINE 0

The expected behavior is for both the PXE interface (enp5s0) and the MAAS-configured VLAN interface (enp5s0.100) to remain configured after deployment.

How to test this fix

Note: I tested this on MAAS 3.5.12 (snap) which uses Ubuntu 22.04 (Jammy).

  1. Download this custom image (built with packer-maas) that includes this cloud-init patch.
  2. Upload it to your MAAS environment.
maas $PROFILE boot-resources create \
    name='custom/packer-custom-ubuntu' \
    title='Ubuntu 22.04 (patched: cloud-init vendor-merge)' \
    architecture='amd64/generic' \
    filetype='tgz' \
    content@=ubuntu-jammy-cloudinit-merge-user-vendor-fix.tar.gz
  1. Deploy a VM with the custom image to memory and the custom cloud config.
  2. SSH into machine and check that networking is set up properly.

You should see networking is set up properly now:

ubuntu@octopus-memory:~$ ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp5s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:16:3e:2d:fb:b3 brd ff:ff:ff:ff:ff:ff
    inet 10.239.17.2/24 brd 10.239.17.255 scope global enp5s0
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fe2d:fbb3/64 scope link 
       valid_lft forever preferred_lft forever
3: enp5s0.100@enp5s0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 00:16:3e:2d:fb:b3 brd ff:ff:ff:ff:ff:ff
    inet 10.20.0.1/24 brd 10.20.0.255 scope global enp5s0.100
       valid_lft forever preferred_lft forever
    inet6 fe80::216:3eff:fe2d:fbb3/64 scope link 
       valid_lft forever preferred_lft forever

Related Links

Merge type

  • Squash merge using "Proposed Commit Message"
  • Rebase and merge unique commits. Requires commit messages per-commit each referencing the pull request number (#<PR_NUM>)

When deploying a machine (such as a MAAS node) with a custom user-data
configuration, top-level lists in vendor-data (e.g., `write_files` or
`runcmd`) are silently ignored if the user-data contains the same keys.
This occurs because user-data is loaded first, and the default merging
strategy (`no_replace` with `recurse_list=False`) discards duplicate
keys from subsequent sources.

In MAAS deployments, this behavior breaks networking by completely
dropping the MAAS-generated `write_files` block that writes the netplan
configuration (e.g., `/etc/netplan/50-maas.yaml`), leaving only the
PXE interface configured.

This PR fixes  this by updating `ConfigMerger._get_instance_configs`
to inject a default `merge_how` strategy for `vendor_cloud_config`
and `vendor2_cloud_config`. This updates the strategy to:
`list(append)+dict(no_replace,recurse_list)+str()`

This ensures vendor-defined lists are appended to user-defined lists
rather than being discarded, while maintaining `no_replace` for
dictionary values and strings.

Signed-off-by: Leah Goldberg <leah.goldberg@canonical.com>
@goldberl
goldberl force-pushed the adjust-cloud-config-merge-strategy branch from c0aaba2 to d8eb547 Compare July 14, 2026 16:26
@goldberl
goldberl marked this pull request as ready for review July 14, 2026 18:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant