How to merge profiles' user.vendor-data?

Hello!

Is there a way to “merge” two profiles’ user.vendor-data? Here’s a concrete use case.

I have one profile that sets an apt cache for my containers:

$ lxc profile show aptcache
config:
  user.vendor-data: |
    #cloud-config
    apt:
      proxy: "http://10.22.236.1:3142"
    apt_proxy: "http://10.22.236.1:3142"
description: set up apt caching via 10.22.236.1
devices: {}
name: aptcache

then I have another which creates a user:

$ lxc profile show roadmr
config:
  user.vendor-data: |
    #cloud-config
    users:
      - name: roadmr
        groups: sudo
        shell: /bin/bash
        sudo: ['ALL=(ALL) NOPASSWD:ALL']
    # ensure users shell is installed
    packages:
      - bash
description: roadmr
name: roadmr

however, those two can’t obviously be combined:

lxc init -p default -p roadmr -p aptcache ubuntu:16.04 borked-container

In this case, only aptcache is applied and I don’t have my nice precreated user :frowning:

Clearly I could just have a single user.vendor-data in another profile, but, his example is artificial to illustrate the situation; in practice, I don’t always want apt caching in containers where I do want the user created, and viceversa. And even if creating “combined” user.vendor-data were acceptable, it quickly becomes unmanageable as the number of possible combinations grow. For instance, I have another profile which enables security.nesting and installs squashfuse via user.vendor-data, which I can’t combine with the above two. For these three profiles, I’d need to create 4 different profiles to cover all combinations.

So my question is: Is there a way to have lxd more intelligently combine these user.vendor-data (or really any cloud-config)?

Thanks in advance!

Nope, to LXD those are just free-form text config keys. Whichever is applied last wins.

There is ongoing work to have cloud-init use the /dev/lxd/sock interface to get the LXD configuration for the container. With that, cloud-init could in theory grow support for any number of “user.vendor-data.*” keys, applying them in order.

Thanks for replying! OK, I’ll keep an eye out for those changes if they happen, and resign myself to merging my configs manually in the meantime.

Thanks!

Has it been done ? It seem user.vendor-data doesn’t merge with other when chaining profiles, but each user.vendor-data replaces the previous profile’s (LXD 3.0.3)

user-data and vendor-data get merged together by cloud-init.

LXD doesn’t do any merging of keys coming from profiles, whichever defines the key last wins.
That’s why we usually suggest having your profile use vendor-data and having your container use user-data so those two get combined by cloud-init.

So it doesn’t merge them when chaining profile. It would be useful…

That would require LXD to understand all the potential syntax for all the possible keys which isn’t something that would be practical to do and would also break current behavior for anyone requiring the current full override behavior.

cloud-init allows for more than just YAML input and even in the yaml case, it’s not always obvious how we’d merge the data. Within cloud-init it’s fine as the different plugins get exposed both data (vendor and user) can can decide what’s appropriate.

1 Like

Did you see the Coud-Init manual section “Merging User-Data Sections”? It seems to offer the ability for custom merging.

Since previously the merging algorithm was very simple and would only overwrite and not append lists, or strings, and so on it was decided to create a new and improved way to merge dictionaries (and their contained objects) together in a way that is customizable, thus allowing for users who provide cloud-config user-data to determine exactly how their objects will be merged.

The last subsection “Other Uses” says

In addition to being used for merging user-data sections, the default merging algorithm for merging ‘conf.d’ yaml files (which form an initial yaml config for cloud-init) was also changed to use this mechanism so its full benefits (and customization) can also be used there as well. Other places that used the previous merging are also, similarly, now extensible (metadata merging, for example).
Sorry I don't enough experience to tell you for certain whether this means it could be applied to your vendor data, or if it did, how much initial investment that would take.
1 Like

I also would prefer if cloud-init related definitions could be merged as described in the manual cited by @craigphicks. While profiles could use user.vendor-data and containers could use user.user-data, this does not solve problems with combinations of different (specialized) profiles.
For a small set of profiles/containers it might be possible to duplicate/manually merge these definitions in auxiliary profiles, but this at least partially defeats the purpose of having multiple profiles in the first place.

Having profiles merge rather than override just for this one key would make things quite inconsistent.

There’s also the issue that while most people use the YAML #cloud-config format, cloud-init actually allows many more formats which cannot merge as easily (the key could store a raw shell script, a base64 archive, a multi-part entry, … don’t remember exactly what’s supported, but there are many options).

Instead we’ve been pushing for the cloud-init team to start by implementing a native LXD provider module. This will have cloud-init use /dev/lxd to retrieve the LXD configuration rather than relying on file templating to dump data into place.

Once that step is done, we’re hoping to transition user.user-data, user.vendor-data, user.meta-data and user.network-config into their own cloud-init key namespace to indicate actual 1st party support of this.

That cloud-init provider code could then most likely be made to inspect all of the cloud-init keys and allow for something like:

  • cloud-init.vendor.foo
  • cloud-init.vendor.bar
  • cloud-init.vendor.baz
  • cloud-init.vendor
  • cloud-init.user.foo
  • cloud-init.user.bar
  • cloud-init.user

with cloud-init itself handling the merging and figuring out the application order.

4 Likes

Has there been any changes to this?
trying to have 2 profiles with a #cloud-config work together, basically appending one another.
Which i take is what this discussion was about?

Nope, no change, your two options still are user.user-data and user.vendor-data, if you need more than two then you’re out of luck.

There’s been an item for proper integration of LXD with cloud-init on the cloud-init team’s roadmap for a couple of years, though that work always seems to get scheduled and then not actually make it… Once they finally get to implement their side of it, we’ll have more options to provide configuration to cloud-init than what we do today.

how does the user.vendor-data work?
i could never get both it and user.user-data to work together,
not with yaml or jsonp (which i don’t get if it’s just for runcmd or not).
i am guessing i must be doing something wrong?

vendor-data uses the same format as user-data and gets merged with it. So as long as you don’t use the same config keys in both, cloud-init will merge them together

Ah that’s the problem then, so if one has “users” the other can’t append to it but it skipped or replace it.

Well hope the day comes when they add the support you mentioned before as it seems quite useful.

Thanks!

Following on from previous questions – how is user.network-config merged (in my experiment on 4.0.9) it didn’t seem to be. And also is there much change in the situation with version 5; I’ve noticed the rename to cloud-init.vendor-data, cloud-init.user-data and cloud-init.network-config happened.

Still don’t know in detail how all magic works, but when I add the merge_how section (https://cloudinit.readthedocs.io/en/latest/topics/merging.html) it works – without the merge_how, one file wins!

config:
  user.user-data: |
    #cloud-config
    merge_how:
    - name: list
      settings: [append]
    - name: dict
      settings: [no_replace, recurse_list]
...etc

Realising that data is now provided though a dedicated lxd datasource (and not files) helped with the debugging!
https://cloudinit.readthedocs.io/en/latest/topics/datasources/lxd.html

2 Likes

@olx Would you mind sharing your example profiles for how this works for you? Does ordering matter?

@stgraber would this be something that could be added to the documentation around cloud-init and profiles? It seems there is a path forward now and it would be a good way to show you could layer profiles. my use case is to have a user profile and then an app profile … I am trying to follow a dry strategy. In the past I just had external template that I would generate a bunch of different profiles but that is a clunky long term strategy.

@ru-fu the merge behafvior for user-data + vendor-data is something that could probably be added to our docs.

Sure!

So cloud-init.user-data and cloud-init.vendor-data get merged together.

What about user.meta-data?
According to the current Instance options docs, that one is also appended:

Additionally, those user keys have become common with images (support isn’t guaranteed):

KEY TYPE DEFAULT DESCRIPTION
user.meta-data string - Cloud-init meta-data, content is appended to seed value

But that might be outdated?