Skip to content
This repository was archived by the owner on Jan 23, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions packages/jumpstarter-driver-corellium/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Corellium Jumpstarter Driver

A Jumpstarter driver that manages virtual devices in [Corellium](https://corellium.com).

It implements the following interfaces:

* PowerInterface

## Usage

Check the [examples folder](./examples) for example files.

### Config Exporter

You can run an exporter by running: `jmp exporter shell -c $file`:

```yml
apiVersion: jumpstarter.dev/v1alpha1
kind: ExporterConfig
# endpoint and token are intentionally left empty
metadata:
namespace: default
name: corellium-demo
endpoint: ""
token: ""
export:
rd1ae:
type: jumpstarter_driver_corellium.driver.Corellium
config:
project_id: "778f00af-5e9b-40e6-8e7f-c4f14b632e9c"
device_name: "jmp-rd1ae"
device_flavor: "kronos"
```

```yml
apiVersion: jumpstarter.dev/v1alpha1
kind: ExporterConfig
# endpoint and token are intentionally left empty
metadata:
namespace: default
name: corellium-demo
endpoint: ""
token: ""
export:
rd1ae:
type: jumpstarter_driver_corellium.driver.Corellium
config:
project_id: "778f00af-5e9b-40e6-8e7f-c4f14b632e9c"
device_name: "jmp-rd1ae"
device_flavor: "kronos"
device_os: "1.0"
device_build: "Critical Application Monitor (Baremetal)"
```
18 changes: 18 additions & 0 deletions packages/jumpstarter-driver-corellium/examples/exporter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
apiVersion: jumpstarter.dev/v1alpha1
kind: ExporterConfig
# endpoint and token are intentionally left empty
metadata:
namespace: default
name: corellium-demo
endpoint: ""
token: ""
export:
rd1ae:
type: jumpstarter_driver_corellium.driver.Corellium
config:
project_id: "778f00af-5e9b-40e6-8e7f-c4f14b632e9c"
device_name: "jmp-rd1ae"
device_flavor: "kronos"
# optional
device_os: "1.1.1"
device_build: "Critical Application Monitor (Baremetal)"
5 changes: 5 additions & 0 deletions packages/jumpstarter-driver-corellium/fixtures/http/403.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"error": "Invalid or missing authorization token",
"errorID": "PermissionDenied",
"originalError": "Invalid or missing authorization token"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"id": "7f4f241c-821f-4219-905f-c3b50b0db5dd"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"error": "Unsupported device model",
"errorID": "UserError",
"field": "flavor"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"error": "Instance with instanceId=7f4f241c-821f-4219-905f-c3b50b0db5dd not found",
"errorID": "InstanceNotFound",
"name": "Instance",
"params": {
"instanceId": "7f4f241c-821f-4219-905f-c3b50b0db5dd"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
on
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"error": "No instance associated with this value",
"errorID": "UserError",
"field": "InstanceId"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[
{
"type": "iot",
"name": "rpi4b",
"flavor": "rpi4b",
"description": "Raspberry Pi 4",
"model": "rpi4b",
"peripherals": false,
"quotas": {
"cores": 4,
"cpus": 4
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[
{
"id": "d59db33d-27bd-4b22-878d-49e4758a648e",
"name": "Default Project",
"settings": {
"internet-access": true,
"dhcp": true
},
"quotas": {
"cores": 30,
"instances": 75,
"ram": 184320
},
"quotasUsed": {
"cores": 2,
"instances": 7,
"ram": 2048,
"gpu": 0
}
},
{
"id": "e2fdb33c-37ae-4b22-878d-49e4758a51f0",
"name": "OtherProject",
"settings": {
"internet-access": true,
"dhcp": true
},
"quotas": {
"cores": 30,
"instances": 75,
"ram": 184320
},
"quotasUsed": {
"cores": 2,
"instances": 7,
"ram": 2048,
"gpu": 0
}
}
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"error": "Found no matching Projects",
"errorID": "UserError",
"field": "Project"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"token": "a}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix invalid JSON syntax in test fixture.

The JSON file contains syntax errors: the string value for "token" is missing a closing quotation mark, and the JSON object is missing a closing brace. This will cause parsing errors when used.

Apply this diff to correct the JSON syntax:

-{"token": "a}
+{"token": "a"}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{"token": "a}
{"token": "a"}
🧰 Tools
🪛 Biome (1.9.4)

[error] 1-1: Missing closing quote

The closing quote must be on the same line.

(parse)


[error] 1-1: expected } but instead the file ends

the file ends here

(parse)

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"token": "session-token",
"expiration": "2022-03-20T01:50:10.000Z"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from jumpstarter_driver_composite.client import CompositeClient


class CorelliumClient(CompositeClient):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
from typing import Optional

import requests

from .exceptions import CorelliumApiException
from .types import Device, Instance, Project, Session


class ApiClient:
"""
Corellium ReST API client used by the Corellium driver.
"""
session: Session
req: requests.Session

def __init__(self, host: str, token: str) -> None:
"""
Initializes a new client, containing a
"""
self.host = host
self.token = token
self.session = None
self.req = requests.Session()

@property
def baseurl(self) -> str:
"""
Return the baseurl path for API calls.
"""
return f'https://{self.host}/api'

def login(self) -> None:
"""
Login against Corellium's ReST API.

Set an internal Session object instance to be used
in other API calls that require authentication.

It uses the global requests objects so a new session can be generated.
"""
data = {
'apiToken': self.token
}

try:
res = requests.post(f'{self.baseurl}/v1/auth/login', json=data)
data = res.json()
res.raise_for_status()
except (requests.exceptions.RequestException, requests.exceptions.HTTPError) as e:
raise CorelliumApiException(data.get('error', str(e))) from e

self.session = Session(**data)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
self.req.headers.update(self.session.as_header())

def get_project(self, project_ref: str = 'Default Project') -> Optional[Project]:
"""
Retrieve a project based on project_ref, which is either its id or name.
"""
try:
res = self.req.get(f'{self.baseurl}/v1/projects')
data = res.json()
res.raise_for_status()
except requests.exceptions.RequestException as e:
raise CorelliumApiException(data.get('error', str(e))) from e

for project in data:
if project['name'] == project_ref or project['id'] == project_ref:
return Project(id=project['id'], name=project['name'])

return None

def get_device(self, model: str) -> Optional[Device]:
"""
Get a device spec from Corellium's list based on the model name.

A device object is used to create a new virtual instance.
"""
try:
res = self.req.get(f'{self.baseurl}/v1/models')
data = res.json()
res.raise_for_status()
except requests.exceptions.RequestException as e:
raise CorelliumApiException(data.get('error', str(e))) from e

for device in data:
if device['model'] == model:
return Device(**device)

return None

def create_instance(self, name: str, project: Project, device: Device, os_version: str, os_build: str) -> Instance:
"""
Create a new virtual instance from a device spec.
"""
data = {
'name': name,
'project': project.id,
'flavor': device.flavor,
'os': os_version,
'osbuild': os_build,
}

try:
res = self.req.post(f'{self.baseurl}/v1/instances', json=data)
data = res.json()
res.raise_for_status()
except requests.exceptions.RequestException as e:
raise CorelliumApiException(data.get('error', str(e))) from e

return Instance(**data)

def get_instance(self, instance_ref: str) -> Optional[Instance]:
"""
Retrieve an existing instance by its name.

Return None if it does not exist.
"""
try:
res = self.req.get(f'{self.baseurl}/v1/instances')
data = res.json()
res.raise_for_status()
except requests.exceptions.RequestException as e:
raise CorelliumApiException(data.get('error', str(e))) from e

for instance in data:
if instance['name'] == instance_ref or instance['id'] == instance_ref:
return Instance(id=instance['id'], state=instance['state'])

return None

def set_instance_state(self, instance: Instance, instance_state: str) -> None:
"""
Set the virtual instance state from corellium.

Valid instance state values:

- on
- off
- booting
- deleting
- creating
- restoring
- paused
- rebooting
- error
"""
data = {
'state': instance_state
}

try:
res = self.req.put(f'{self.baseurl}/v1/instances/{instance.id}/state', json=data)
data = res.json() if res.status_code != 204 else None
res.raise_for_status()
except requests.exceptions.RequestException as e:
msgerr = data if data is not None else str(e)

raise CorelliumApiException(msgerr) from e

def destroy_instance(self, instance: Instance) -> None:
"""
Delete a virtual instance.

Does not return anything since Corellium's API return a HTTP 204 response.
"""
try:
res = self.req.delete(f'{self.baseurl}/v1/instances/{instance.id}')
data = res.json() if res.status_code != 204 else None
res.raise_for_status()
except requests.exceptions.RequestException as e:
msgerr = data if data is not None else str(e)

raise CorelliumApiException(msgerr) from e
Loading