Working with Cloud-init

View as Markdown
# Imports (run once)
from pathlib import Path
from air_sdk import AirApi
from air_sdk.endpoints import Node, Simulation, UserConfig
# 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.
# 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.

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.

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.

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.

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.

# 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).

# 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()