User data formats

User data is opaque configuration data provided by a platform to an instance at launch configure the instance. User data can be one of the following types.

Cloud config data

Cloud-config is the preferred user data format. The cloud config format is a declarative syntax which uses YAML version 1.1 with keys which describe desired instance state. Cloud-config can be used to define how an instance should be configured in a human-friendly format.

These things may include:

  • performing package upgrades on first boot

  • configuration of different package mirrors or sources

  • initial user or group setup

  • importing certain SSH keys or host keys

  • and many more…

See the Cloud config examples section for a commented set of examples of supported cloud config formats.

Begins with: #cloud-config or Content-Type: text/cloud-config when using a MIME archive.

Note

Cloud config data can also render cloud instance metadata variables using jinja templates.

User data script

Typically used by those who just want to execute a shell script.

Begins with: #! or Content-Type: text/x-shellscript when using a MIME archive.

User data scripts can optionally render cloud instance metadata variables using jinja templates.

Example script

Create a script file myscript.sh that contains the following:

#!/bin/sh
echo "Hello World.  The time is now $(date -R)!" | tee /root/output.txt

Now run:

$ euca-run-instances --key mykey --user-data-file myscript.sh ami-a07d95c9

Kernel command line

When using the NoCloud datasource, users can pass user data via the kernel command line parameters. See the NoCloud datasource and Kernel command line documentation for more details.

Gzip compressed content

Content found to be gzip compressed will be uncompressed. The uncompressed data will then be used as if it were not compressed. This is typically useful because user data is limited to ~16384 [1] bytes.

MIME multi-part archive

This list of rules is applied to each part of this multi-part file. Using a MIME multi-part file, the user can specify more than one type of data.

For example, both a user data script and a cloud-config type could be specified.

Supported content-types are listed from the cloud-init subcommand make-mime:

$ cloud-init devel make-mime --list-types

Example output:

cloud-boothook
cloud-config
cloud-config-archive
cloud-config-jsonp
jinja2
part-handler
x-include-once-url
x-include-url
x-shellscript
x-shellscript-per-boot
x-shellscript-per-instance
x-shellscript-per-once

Helper subcommand to generate MIME messages

The cloud-init make-mime subcommand can also generate MIME multi-part files.

The make-mime subcommand takes pairs of (filename, “text/” mime subtype) separated by a colon (e.g., config.yaml:cloud-config) and emits a MIME multipart message to stdout.

Examples

Create user data containing both a cloud-config (config.yaml) and a shell script (script.sh)

$ cloud-init devel make-mime -a config.yaml:cloud-config -a script.sh:x-shellscript > userdata

Create user data containing 3 shell scripts:

  • always.sh - run every boot

  • instance.sh - run once per instance

  • once.sh - run once

$ cloud-init devel make-mime -a always.sh:x-shellscript-per-boot -a instance.sh:x-shellscript-per-instance -a once.sh:x-shellscript-per-once

include file

This content is an include file.

The file contains a list of URLs, one per line. Each of the URLs will be read and their content will be passed through this same set of rules, i.e., the content read from the URL can be gzipped, MIME multi-part, or plain text. If an error occurs reading a file the remaining files will not be read.

Begins with: #include or Content-Type: text/x-include-url when using a MIME archive.

cloud-boothook

One line #cloud-boothook header and then executable payload.

This is run very early on the boot process, during the Network boot stage, even before cc_bootcmd.

This can be used when something has to be configured very early on boot, potentially on every boot, with less convenience as cc_bootcmd but more flexibility.

Note

Boothooks are executed on every boot. The environment variable INSTANCE_ID will be set to the current instance ID. INSTANCE_ID can be used to implement a once-per-instance type of functionality.

Begins with: #cloud-boothook.

Example with simple script

#cloud-boothook
#!/bin/sh
echo 192.168.1.130 us.archive.ubuntu.com > /etc/hosts

Example of once-per-instance script

#cloud-boothook
#!/bin/sh

PERSIST_ID=/var/lib/cloud/first-instance-id
_id=""
if [ -r $PERSIST_ID ]; then
  _id=$(cat /var/lib/cloud/first-instance-id)
fi

if [ -z $_id ]  || [ $INSTANCE_ID != $_id ]; then
  echo 192.168.1.130 us.archive.ubuntu.com >> /etc/hosts
fi
sudo echo $INSTANCE_ID > $PERSIST_ID

Part-handler

This is a part-handler: It contains custom code for either supporting new mime-types in multi-part user data, or overriding the existing handlers for supported mime-types. It will be written to a file in /var/lib/cloud/data based on its filename (which is generated).

This must be Python code that contains a list_types function and a handle_part function. Once the section is read the list_types method will be called. It must return a list of mime-types that this part-handler handles. Since MIME parts are processed in order, a part-handler part must precede any parts with mime-types it is expected to handle in the same user data.

The handle_part function must be defined like:

def handle_part(data, ctype, filename, payload):
  # data = the cloudinit object
  # ctype = "__begin__", "__end__", or the mime-type of the part that is being handled.
  # filename = the filename of the part (or a generated filename if none is present in mime data)
  # payload = the parts' content

Cloud-init will then call the handle_part function once before it handles any parts, once per part received, and once after all parts have been handled. The '__begin__' and '__end__' sentinels allow the part handler to do initialisation or teardown before or after receiving any parts.

Begins with: #part-handler or Content-Type: text/part-handler when using a MIME archive.

Example

 1#part-handler
 2
 3def list_types():
 4    # return a list of mime-types that are handled by this module
 5    return(["text/plain", "text/go-cubs-go"])
 6
 7def handle_part(data, ctype, filename, payload):
 8    # data: the cloudinit object
 9    # ctype: '__begin__', '__end__', or the specific mime-type of the part
10    # filename: the filename for the part, or dynamically generated part if
11    #           no filename is given attribute is present
12    # payload: the content of the part (empty for begin or end)
13    if ctype == "__begin__":
14       print("my handler is beginning")
15       return
16    if ctype == "__end__":
17       print("my handler is ending")
18       return
19
20    print(f"==== received ctype={ctype} filename={filename} ====")
21    print(payload)
22    print(f"==== end ctype={ctype} filename={filename}")

Also, this blog post offers another example for more advanced usage.

Disabling user data

Cloud-init can be configured to ignore any user data provided to instance. This allows custom images to prevent users from accidentally breaking closed appliances. Setting allow_userdata: false in the configuration will disable cloud-init from processing user data.