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