> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.nvidia.com/dsx-air/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.nvidia.com/dsx-air/_mcp/server.

# Working with Cloud-init

```python
# Imports (run once)
from pathlib import Path

from air_sdk import AirApi
from air_sdk.endpoints import Node, Simulation, UserConfig
```

```python
# Authentication (run once)
api = AirApi.with_ngc_config()
# OR api = AirApi.with_api_key(api_key="...")
# OR api = AirApi.with_device_login(email="...", org_num="...")
#    ^ use in terminal only — not supported in Jupyter notebooks
```

## Creating cloud-init user configs

`api.user_configs.create()` requires `name`, `kind`, and `content`.

The `content` argument is flexible and accepts any of:

* a literal string (used verbatim, **never** interpreted as a file path),
* a `pathlib.Path` pointing to a file to read, or
* an open file handle.

```python
# Read user-data from a file by passing a Path
user_data: UserConfig = api.user_configs.create(
    name='cl-init-userdata',
    kind=UserConfig.KIND_CLOUD_INIT_USER_DATA,  # 'cloud-init-user-data'
    content=Path('../files/cloud_init/user-data.yaml'),
)

# Read meta-data from an open file handle
with open('../files/cloud_init/meta-data.yaml') as meta_data_file:
    meta_data: UserConfig = api.user_configs.create(
        name='cl-init-metadata',
        kind=UserConfig.KIND_CLOUD_INIT_META_DATA,  # 'cloud-init-meta-data'
        content=meta_data_file,
    )

# `content` may also be a literal string
inline_meta_data: UserConfig = api.user_configs.create(
    name='cl-init-metadata-inline',
    kind=UserConfig.KIND_CLOUD_INIT_META_DATA,
    content='instance-id: server3\nlocal-hostname: server3\n',
)

print(user_data.id, meta_data.id, inline_meta_data.id)
```

```
b19663c3-2551-4b50-939d-61703152fff7 2e97367b-8fbb-4cbd-90b0-5a8f988f24f1 bc78c5b1-e892-4ce2-9403-139c24e75e80
```

## Setting up a simulation with nodes

Cloud-init configs are assigned to nodes within a simulation. Here we create a small
simulation with two nodes to assign the configs to.

```python
sim: Simulation = api.simulations.create(name='cloud-init-demo')

ubuntu_2204 = next(api.images.list(search='generic/ubuntu2204'))

node1: Node = sim.nodes.create(name='server1', image=ubuntu_2204)
node2: Node = sim.nodes.create(name='server2', image=ubuntu_2204)

print(node1.id, node2.id)
```

```
11f291b7-5dc6-4b45-92c5-d55f2d5c1df3 c9493622-625d-4d25-ab8f-0da023d68286
```

## Assigning cloud-init configs to nodes

Assignments are made with the v3 bulk-assign API, `simulation.node_bulk_assign()`.
Each entry maps a `node` to its `user_data` and/or `meta_data` config. Values may be
`UserConfig` objects, config IDs, or `None` to clear an existing assignment.

`node_bulk_assign()` accepts many entries in a single call, so you can configure a
whole simulation at once and reuse the same `UserConfig` across nodes.

```python
sim.node_bulk_assign(
    nodes=[
        {'node': node1, 'user_data': user_data, 'meta_data': meta_data},
        {'node': node2, 'user_data': user_data},  # meta_data left unset
    ],
)
```

## Reading back an assignment

A node's current cloud-init assignment is available on the `node.cloud_init` property,
which exposes the assigned `user_data` and `meta_data` configs.

```python
cloud_init = node1.cloud_init
print('user_data:', cloud_init.user_data)
print('meta_data:', cloud_init.meta_data)
```

```
user_data: 
```

```
UserConfig(id='b19663c3-2551-4b50-939d-61703152fff7', name='cl-init-userdata', kind='cloud-init-user-data')
meta_data: 
```

```
UserConfig(id='2e97367b-8fbb-4cbd-90b0-5a8f988f24f1', name='cl-init-metadata', kind='cloud-init-meta-data')
```

## Updating cloud-init content

A node's assignment points to a `UserConfig` rather than copying its content, so
updating a config's `content` automatically applies to every node it's assigned to —
no reassignment needed.

```python
user_data.update(content=Path('../files/cloud_init/user-data.yaml'))
```

## When does cloud-init take effect?

Cloud-init is consumed on the node's **next boot**. Assign your configs *before*
starting the simulation, or **rebuild** the node afterward for the changes to take
effect on an already-running node.

## Clearing an assignment

Pass `None` for a config in `node_bulk_assign()` to remove it from a node — set a
single field to clear just that one, or both to clear the node's cloud-init entirely.

```python
# Clear only the meta-data assignment, leaving user-data in place
sim.node_bulk_assign(nodes=[{'node': node1, 'meta_data': None}])

# Clear all cloud-init assignments from the node
sim.node_bulk_assign(
    nodes=[{'node': node1, 'user_data': None, 'meta_data': None}],
)
```

## Cleaning up

Delete the simulation and the `UserConfig` resources created above. Deleting the
simulation removes its nodes; the configs are separate resources and must be deleted
explicitly (do this only if they are not assigned to other simulations).

```python
# Deleting the simulation also removes its nodes
sim.delete()

# UserConfigs are separate resources — delete them explicitly
user_data.delete()
meta_data.delete()
inline_meta_data.delete()
```