Images

View as Markdown

CREATE

Creating a new image.

from air_sdk import AirApi
from air_sdk.endpoints.images import Image
api = AirApi.with_ngc_config()
# Or use SAK directly
# api = AirApi.with_api_key(api_key='nvapi-xyz')
image: Image = api.images.create(
name='cumulus-vx-1.2.3',
default_username='user',
default_password='password',
version='1.0.0',
mountpoint='/mnt/my-image',
cpu_arch='x86',
includes_air_agent=True,
)
image.dict()
{'id': '935b994e-9e1e-4d85-9275-10f7b3111cd9',
'name': 'cumulus-vx-1.2.3',
'created': datetime.datetime(2025, 4, 22, 20, 3, 22, 744128, tzinfo=datetime.timezone.utc),
'modified': datetime.datetime(2025, 4, 22, 20, 3, 22, 744142, tzinfo=datetime.timezone.utc),
'published': False,
'includes_air_agent': True,
'cpu_arch': 'x86',
'default_username': 'user',
'default_password': 'password',
'version': '1.0.0',
'mountpoint': '/mnt/my-image',
'emulation_type': [],
'emulation_version': '',
'provider': 'VM',
'minimum_resources': {'cpu': 1, 'memory': 1024, 'storage': 10},
'upload_status': 'READY',
'last_uploaded_at': None,
'size': 0,
'hash': ''}
image_id = str(image.id)
image_id
'935b994e-9e1e-4d85-9275-10f7b3111cd9'

Create and Upload in One Step

You can also create an image and upload the file content in a single operation by providing the filepath parameter to create():

from air_sdk import AirApi
from air_sdk.endpoints.images import Image
api = AirApi.with_api_key(api_key='nvapi-xyz')
image: Image = api.images.create(
name='cumulus-vx-1.2.3',
default_username='user',
default_password='password',
version='1.0.0',
mountpoint='/mnt/my-image',
cpu_arch='x86',
includes_air_agent=True,
filepath='/home/user/images/cumulus-vx-5.0.0.qcow2',
)

GET

Retrieve a specific Image

from air_sdk.endpoints.images import Image
image: Image = api.images.get(image_id)
image
Image(name='cumulus-vx-1.2.3', version='1.0.0', upload_status='READY')

List/Filter/Order/Search an iterable of Images

We can query for images with specific characteristics.

name_substring = 'cumulus-vx'
for image in api.images.list(search=name_substring, ordering='-name', cpu_arch='x86'):
print(image.name.ljust(25), image.created, image.upload_status)
cumulus-vx-5.6.0 2025-04-14 20:39:00.640224+00:00 READY

UPDATE

Update specific fields on an individual image.

from air_sdk.endpoints.images import Image
image: Image = api.images.get(image_id)
# Perform the update
image.update(version='1.0.1')
image
Image(name='cumulus-vx-1.2.3', version='1.0.1', upload_status='READY')

UPLOAD FILE CONTENT

Upload the file content of the image (e.g. and cumulus-vx-1.2.3.iso) to Air.

import time
from pathlib import Path
from air_sdk.endpoints.images import Image
local_file_path = Path.home() / 'cumulus-vx-1.2.3.iso'
image: Image = api.images.get(image_id)
image.upload(local_file_path)
while image.upload_status == 'VERIFYING':
image.refresh()
time.sleep(1) # The Air API will asynchronously verify the image upload
image
Image(name='cumulus-vx-1.2.3', version='1.0.1', upload_status='COMPLETE')

Reset/clear the file content associated with an Image

If you want to upload different content to the image you must first call clear_upload to clear the uploaded content currently associated with the image. This step is in place to protect currently uploaded images.

Parallel uploads for large files

For large files, you can speed up uploads by using multiple parallel workers. The SDK automatically chunks files into ~100MB parts and uploads them to S3.

import time
from pathlib import Path
from air_sdk.endpoints.images import Image
large_file_path = Path.home() / 'large-cumulus-image.qcow2'
image: Image = api.images.get(image_id)
# Upload with 4 parallel workers (recommended for large files on fast connections)
# Each worker uploads a ~100MB part concurrently
image.upload(filepath=large_file_path, max_workers=4)
# Or with custom timeout per part (default is 5 minutes per part)
# image.upload(filepath=large_file_path, max_workers=4, timeout=timedelta(minutes=10))
while image.upload_status in ['VERIFYING', 'UPLOADING', 'VALIDATING']:
image.refresh()
print(f'Status: {image.upload_status}')
time.sleep(1)
print(f'Upload complete! Status: {image.upload_status}')
new_file = Path.home() / 'cumulus-vx-1.2.3-new.iso'
print('1. Status:', image.upload_status, 'Hash:', image.hash)
image.clear_upload()
print('2. Status:', image.upload_status, 'Hash:', image.hash)
image.upload(new_file)
while image.upload_status == 'VERIFYING':
image.refresh()
time.sleep(1)
print('3. Status:', image.upload_status, 'Hash:', image.hash)
1. Status: COMPLETE Hash: 1894a19c85ba153acbf743ac4e43fc004c891604b26f8c69e1e83ea2afc7c48f
2. Status: READY Hash:
3. Status: COMPLETE Hash: ec7e5b4a32e4c00a786ded0a1632990716c2447f6f800fe96d253f91850e0ab3

Verifying / Checking Image Content

There are two ways to verify the content of an uploaded image.

  1. Compare the hash of an image with the hash of a local file
  2. Download the contents of the uploaded image and inspect the contents

1. Comparing hashes

An Image will have a populated hash when the Image has an associated file upload. This hash is the SHA256 hash of the file calculated using the air_sdk.utils.sha256_file method.

If you have a file locally, you can use this sha256_file method to determine your local hash and can compare this to the hash associated with the Image instance to see if the contents are identical.

from pathlib import Path
from air_sdk.endpoints.images import Image
from air_sdk.utils import sha256_file
local_file_path = Path.home() / 'cumulus-vx-1.2.3-new.iso'
local_file_hash = sha256_file(local_file_path)
print('Local hash:', local_file_hash)
image: Image = api.images.get(image_id)
print('Image hash:', image.hash)
if image.hash == local_file_hash:
print('The image content is identical to the local file content')
else:
print('Content is different')
Local hash: ec7e5b4a32e4c00a786ded0a1632990716c2447f6f800fe96d253f91850e0ab3
Image hash: ec7e5b4a32e4c00a786ded0a1632990716c2447f6f800fe96d253f91850e0ab3
The image content is identical to the local file content

2. Download the file contents

If you have permission to perform this operation, you can download the contents of the image to a local file and inspect its contents.

import os
from pathlib import Path
from air_sdk.endpoints.images import Image
path_to_new_file_that_will_be_created = Path.home() / 'downloaded-image.iso'
image: Image = api.images.get(image_id)
image.download(path_to_new_file_that_will_be_created)
print('Expected size of image:', image.size)
print('Size of downloaded file:', os.path.getsize(path_to_new_file_that_will_be_created))
Expected size of image: 25
Size of downloaded file: 25

DELETE

Delete an image

from air_sdk import AirApi
from air_sdk.endpoints.images import Image
image: Image = api.images.get(image_id)
image.delete()