Skip to content

feat(helpers): support custom datasource vendor merge strategies - #6944

Open
goldberl wants to merge 1 commit into
canonical:mainfrom
goldberl:fix/maas-vendor-data-merge
Open

feat(helpers): support custom datasource vendor merge strategies#6944
goldberl wants to merge 1 commit into
canonical:mainfrom
goldberl:fix/maas-vendor-data-merge

Conversation

@goldberl

@goldberl goldberl commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Proposed Commit Message

feat(helpers): support custom datasource vendor merge strategies

When deploying a MAAS node with custom user-data, top-level lists
in vendor-data (e.g., `write_files`) are dropped if user-data defines
the same keys. This breaks networking by dropping the MAAS Netplan
configuration (`/etc/netplan/50-maas.yaml`).

To fix this without affecting other cloud providers, this change
allows datasources to specify custom merge strategies:

- `ConfigMerger` checks the active datasource (`self._ds`) for a
  `vendor_merge_how` attribute when loading vendor cloud config.
- `DataSourceMAAS` sets `vendor_merge_how` to append list values.

Signed-off-by: Leah Goldberg <leah.goldberg@canonical.com>

Fixes GH-6268
LP: #2158442

Additional Context

This PR fixes an issue where top-level lists in vendor-data (such as write_files) are silently dropped when user-data defines the same keys, which breaks MAAS deployments by wiping out the Netplan network configuration (/etc/netplan/50-maas.yaml).

To resolve this without altering global merge behavior across other cloud providers, ConfigMerger now inspects the active datasource for a vendor_merge_how attribute. DataSourceMAAS sets this attribute to list(append)+dict(no_replace,recurse_list)+str(), ensuring MAAS-generated vendor lists are appended to user lists.

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 for DataSourceMAAS 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. Create a custom image with packer-maas that includes this cloud-init patch (or you can download this one).
  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>)

@blackboxsw blackboxsw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you much for continued work on this @goldberl. The vendor-data vs user-data merge story is an old one with undesirable or poorly documented behavior. I have the general inclination that we don't want to continue to expose more merge_how primitives more publicly than they already are because that syntax is fairly tedious to get correct and has multiple behavioral implications that are complex. If we can think of ways to simplify this config setting from a datasource perspective with aliases for the details merge_how settings, I think that may make this easier to consume by image/platform creators.

One other question I'd like to visit is:

  1. how a user can disable or disregard the merge suggestions provided by MAAS default datasource config. I know cloud-config userdata can with the big hammer:
#cloud-config
vendordata: {enabled: false}

But, I think we may want to provide more discrete handling, since this is merge-order related behavior, not full inclusion or exclusion. I wonder what folks think about defining relative merge order of vendordata or vendordata2 with something like

#cloud-config
vendordata: 
  enabled: true
  userdata_merge: (default|prepend|append|replace|default)

The following could allow users to override the maas vendordata merge defaults to sometime that suits their preference for a given image if it differs from the platform default. This 2nd suggestion likely overengineering for a corner case we don't really need to support at this point in time. But, I think that user-data expressing that it wants vendordata merged with user-data in a specific way may lead to broader support for issues such as #6268. In that case, user-data containing a vendordata: userdata_merge ...` value could establish a practice that duplicated vendordata keys get included/prepended instead of dropped/replaced.

Comment thread cloudinit/sources/DataSourceMAAS.py Outdated
# When merging vendor-data with user-data, append list values
# (e.g. write_files, runcmd) so that vendor networking config
# is not silently dropped when user-data defines the same key.
vendor_merge_how = "list(append)+dict(no_replace,recurse_list)+str()"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can make this datasource config option value simpler to avoid exposing too much flexibility and avoid likelihood of image creator misuse.

What do you think of naming this specifically vendordata_usersdata_merge avoiding all the merge_how syntax and limiting values to something like:

  • default (current cloud-init behavior, replaces and duplicated config keys in VD and UD)

  • prepend (prepend duplicated VD key values before UD)

  • append (append VD values after UD)

I'm hesitant to continue to expose more merge_how operations and syntax 'publicly' as configuration options here because we have found our historic merge_how syntax is fairly confusing to use properly and exposes unique internal behavior changes that seem to require significant testing to assert the merge behaves as intended.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These operations above may also help juju use-cases which provided the juju CLI flag preruncmd and postruncmd to try ordering cloud-init user-data before or after what should have been juju vendor-data to avoid collisions on the duplicated runcmd config values in both platform vendor-data and the client's user-data.

Comment thread cloudinit/helpers.py Outdated
"vendor_cloud_config",
"vendor2_cloud_config",
):
vendor_merge = getattr(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If vendor_merge value is set to anything other than default, as a user I would want a debug message in logs that vendor_data is being prepended before userdata due to datasource_config option X or some such message.

Comment thread cloudinit/sources/DataSourceMAAS.py Outdated
# When merging vendor-data with user-data, append list values
# (e.g. write_files, runcmd) so that vendor networking config
# is not silently dropped when user-data defines the same key.
vendor_merge_how = "list(append)+dict(no_replace,recurse_list)+str()"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, merge_how really only appeared to work within the scope of separate user-data files. The config option should be better named to indicate this merge operation applies to the merge of vendor and userdata somehow.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, name is currently append_vendor_list_data

Comment thread cloudinit/helpers.py
i_cfgs.append(util.read_conf(cc_fn))
cfg = util.read_conf(cc_fn)
if cc_p in (
"vendor_cloud_config",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the changes to helpers, can we get a unittest specifically that exercises differences in vendor_data and vendor_data2. As it stands the unittests defined test only userdata vs vendordata.

@goldberl
goldberl force-pushed the fix/maas-vendor-data-merge branch from 9cb7f50 to 31e0b09 Compare August 26, 2026 21:39
@goldberl
goldberl requested a review from blackboxsw August 26, 2026 21:45
When deploying a MAAS node with custom user-data, top-level lists
in vendor-data (e.g., `write_files`) are dropped if user-data defines
the same keys. This breaks networking by dropping the MAAS Netplan
configuration (`/etc/netplan/50-maas.yaml`).

This change introduces a boolean flag to configure vendor list
merging:

 *  `DataSourceMAAS` sets `append_vendor_list_data = True`.
 *  `ConfigMerger` checks the active datasource for this flag when
    loading `vendor_cloud_config` and `vendor2_cloud_config`, logs a
    debug message, and applies a non-default merge strategy to append
    list items.

Signed-off-by: Leah Goldberg <leah.goldberg@canonical.com>

Fixes canonicalGH-6268
LP: #2158442
@goldberl
goldberl force-pushed the fix/maas-vendor-data-merge branch from 31e0b09 to a09653d Compare August 26, 2026 21:54
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.

2 participants