APIs and SDKs
The complete Cordium API is exposed over gRPC. This is the interface used by the cordium CLI as well as by the Cordium web portal. Cordium currently supports APIs and SDKs for Golang, Typescript/Javascript (see @octelium/sdk and @@octelium/apis) and Python (see octelium-apis and octelium-sdk).
Here is a rather detailed example in Golang:
package main
import (
"context"
"errors"
"fmt"
"io"
"os"
"time"
"github.com/octelium/octelium/apis/main/cordiumv1"
"github.com/octelium/octelium/octelium-go"
"github.com/octelium/octelium/pkg/apiutils/umetav1"
"github.com/octelium/octelium/pkg/grpcerr"
)
func main() {
if err := doMain(context.Background()); err != nil {
panic(err)
}
}
func doMain(ctx context.Context) error {
octeliumC, err := octelium.NewClient(ctx, &octelium.ClientConfig{
Domain: "example.com",
AuthenticationToken: "AUTH_TOKEN",
})
if err != nil {
return err
}
defer octeliumC.Close()
grpcConn, err := octeliumC.GRPC().GetConn(ctx)
if err != nil {
return err
}
c := cordiumv1.NewMainServiceClient(grpcConn)
workspaceC := cordiumv1.NewWorkspaceServiceClient(grpcConn)
{
spaceList, err := c.ListSpace(ctx, &cordiumv1.ListSpaceOptions{})
if err != nil {
return err
}
for _, itm := range spaceList.Items {
fmt.Printf("Space: %s\n", itm.Metadata.Name)
}
}
{
workspaceList, err := c.ListWorkspace(ctx, &cordiumv1.ListWorkspaceOptions{})
if err != nil {
return err
}
for _, itm := range workspaceList.Items {
fmt.Printf("Workspace: %s\n", itm.Metadata.Name)
}
}
{
ws, err := c.CreateWorkspace(ctx, &cordiumv1.Workspace{
Spec: &cordiumv1.Workspace_Spec{
Image: &cordiumv1.Workspace_Spec_Image{
Type: &cordiumv1.Workspace_Spec_Image_Registry_{
Registry: &cordiumv1.Workspace_Spec_Image_Registry{
Url: "node:22-bookworm",
},
},
},
Repository: &cordiumv1.Workspace_Spec_Repository{
Url: "https://github.com/myorg/orders-api",
},
Runtime: &cordiumv1.Workspace_Spec_Runtime{
EnvVars: []*cordiumv1.Workspace_Spec_Runtime_EnvVar{
{
Key: "NODE_ENV",
Type: &cordiumv1.Workspace_Spec_Runtime_EnvVar_Value{
Value: "development",
},
},
},
Tasks: []*cordiumv1.Workspace_Spec_Runtime_Task{
{
Name: "install",
Type: cordiumv1.Workspace_Spec_Runtime_Task_ON_CREATE,
Run: `
npm install
`,
WorkingDir: "/workspace/repo",
},
{
Name: "start-api",
Type: cordiumv1.Workspace_Spec_Runtime_Task_POST_START,
Run: `
npm run dev
`,
WorkingDir: "/workspace/repo",
IsBackground: true,
},
},
},
Limit: &cordiumv1.Workspace_Spec_Limit{
Cpu: &cordiumv1.Workspace_Spec_Limit_CPU{
Millicores: 4000,
},
Memory: &cordiumv1.Workspace_Spec_Limit_Memory{
Megabytes: 8192,
},
},
},
})
if err != nil {
return err
}
ws.Spec.Limit.Storage = &cordiumv1.Workspace_Spec_Limit_Storage{
Megabytes: 20000,
}
ws, err = c.UpdateWorkspace(ctx, ws)
if err != nil {
return err
}
_, err = c.StartWorkspace(ctx, &cordiumv1.StartWorkspaceRequest{
WorkspaceRef: umetav1.GetObjectReference(ws),
})
if err != nil {
return err
}
watcher, err := c.WatchWorkspace(ctx, &cordiumv1.WatchWorkspaceRequest{
WorkspaceRef: umetav1.GetObjectReference(ws),
})
if err != nil {
return err
}
if err := func() error {
for {
msg, err := watcher.Recv()
if err != nil {
return err
}
switch msg.Type.(type) {
case *cordiumv1.WatchWorkspaceResponse_Update_:
cur := msg.GetUpdate().NewItem
old := msg.GetUpdate().OldItem
if cur.Status.State == old.Status.State {
continue
}
switch cur.Status.State {
case cordiumv1.Workspace_Status_PREPARING, cordiumv1.Workspace_Status_RUNNING:
return nil
}
}
}
}(); err != nil {
return err
}
execStream, err := workspaceC.Exec(ctx)
if err != nil {
return err
}
if err := execStream.Send(&cordiumv1.ExecRequest{
Type: &cordiumv1.ExecRequest_Request_{
Request: &cordiumv1.ExecRequest_Request{
WorkspaceRef: umetav1.GetObjectReference(ws),
Command: "ls -la",
WorkingDir: "/",
},
},
}); err != nil {
return err
}
if err := func() error {
for {
select {
case <-ctx.Done():
return nil
default:
msg, err := execStream.Recv()
if err != nil {
if errors.Is(err, io.EOF) || grpcerr.IsCanceled(err) {
return nil
}
time.Sleep(200 * time.Millisecond)
continue
}
switch msg.Type.(type) {
case *cordiumv1.ExecResponse_Exit_:
if msg.GetExit().Code != 0 {
os.Exit(int(msg.GetExit().Code))
}
return nil
case *cordiumv1.ExecResponse_Stdout_:
if _, err := os.Stdout.Write(msg.GetStdout().Data); err != nil {
} else {
os.Stdout.Write([]byte("\n"))
}
case *cordiumv1.ExecResponse_Stderr_:
if _, err := os.Stderr.Write(msg.GetStderr().Data); err != nil {
} else {
os.Stderr.Write([]byte("\n"))
}
}
}
}
return nil
}(); err != nil {
return err
}
_, err = c.StopWorkspace(ctx, &cordiumv1.StopWorkspaceRequest{
WorkspaceRef: umetav1.GetObjectReference(ws),
})
if err != nil {
return err
}
}
return nil
}Here is the same example written in Python:
import asyncio
import sys
from typing import AsyncIterator
import betterproto
from octelium.api.main.cordium.v1 import (
ExecRequest,
ExecRequestRequest,
ListSpaceOptions,
ListWorkspaceOptions,
MainServiceStub,
StartWorkspaceRequest,
StopWorkspaceRequest,
WatchWorkspaceRequest,
Workspace,
WorkspaceServiceStub,
WorkspaceSpec,
WorkspaceSpecImage,
WorkspaceSpecImageRegistry,
WorkspaceSpecLimit,
WorkspaceSpecLimitCpu,
WorkspaceSpecLimitMemory,
WorkspaceSpecLimitStorage,
WorkspaceSpecRepository,
WorkspaceSpecRuntime,
WorkspaceSpecRuntimeEnvVar,
WorkspaceSpecRuntimeTask,
WorkspaceSpecRuntimeTaskType,
WorkspaceStatusState,
)
from octelium.api.main.meta.v1 import Metadata, ObjectReference
from octelium.sdk import AuthConfig, AuthTokenConfig, OcteliumClient, OcteliumClientConfig
def get_object_reference(obj: Workspace) -> ObjectReference:
return ObjectReference(
name=obj.metadata.name,
uid=obj.metadata.uid,
)
async def exec_requests(ws: Workspace) -> AsyncIterator[ExecRequest]:
yield ExecRequest(
request=ExecRequestRequest(
workspace_ref=get_object_reference(ws),
command="ls -la",
working_dir="/",
)
)
async def main() -> None:
async with await OcteliumClient.create(
OcteliumClientConfig(
domain="example.com",
auth=AuthConfig(
type="auth_token",
auth_token=AuthTokenConfig(
token="AUTH_TOKEN"
),
),
)
) as client:
c = MainServiceStub(client._channel)
workspace_c = WorkspaceServiceStub(client._channel)
space_list = await c.list_space(ListSpaceOptions())
for itm in space_list.items:
print(f"Space: {itm.metadata.name}")
workspace_list = await c.list_workspace(ListWorkspaceOptions())
for itm in workspace_list.items:
print(f"Workspace: {itm.metadata.name}")
ws = await c.create_workspace(
Workspace(
metadata=Metadata(
name="my-workspace",
),
spec=WorkspaceSpec(
image=WorkspaceSpecImage(
registry=WorkspaceSpecImageRegistry(
url="node:22-bookworm",
),
),
repository=WorkspaceSpecRepository(
url="https://github.com/myorg/orders-api",
),
runtime=WorkspaceSpecRuntime(
env_vars=[
WorkspaceSpecRuntimeEnvVar(
key="NODE_ENV",
value="development",
),
],
tasks=[
WorkspaceSpecRuntimeTask(
name="install",
type=WorkspaceSpecRuntimeTaskType.ON_CREATE,
run="npm install\n",
working_dir="/workspace/repo",
),
WorkspaceSpecRuntimeTask(
name="start-api",
type=WorkspaceSpecRuntimeTaskType.POST_START,
run="npm run dev\n",
working_dir="/workspace/repo",
is_background=True,
),
],
),
limit=WorkspaceSpecLimit(
cpu=WorkspaceSpecLimitCpu(millicores=4000),
memory=WorkspaceSpecLimitMemory(megabytes=8192),
),
),
)
)
ws.spec.limit.storage = WorkspaceSpecLimitStorage(megabytes=20000)
ws = await c.update_workspace(ws)
await c.start_workspace(
StartWorkspaceRequest(workspace_ref=get_object_reference(ws))
)
async for msg in c.watch_workspace(
WatchWorkspaceRequest(workspace_ref=get_object_reference(ws))
):
field_name, _ = betterproto.which_one_of(msg, "type")
if field_name != "update":
continue
cur = msg.update.new_item
old = msg.update.old_item
if cur.status.state == old.status.state:
continue
if cur.status.state in (
WorkspaceStatusState.PREPARING,
WorkspaceStatusState.RUNNING,
):
break
async for msg in workspace_c.exec(exec_requests(ws)):
field_name, _ = betterproto.which_one_of(msg, "type")
if field_name == "exit":
if msg.exit.code != 0:
sys.exit(msg.exit.code)
break
elif field_name == "stdout":
sys.stdout.buffer.write(msg.stdout.data)
sys.stdout.buffer.write(b"\n")
sys.stdout.buffer.flush()
elif field_name == "stderr":
sys.stderr.buffer.write(msg.stderr.data)
sys.stderr.buffer.write(b"\n")
sys.stderr.buffer.flush()
await c.stop_workspace(
StopWorkspaceRequest(workspace_ref=get_object_reference(ws))
)
if __name__ == "__main__":
asyncio.run(main())