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:
1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "io"
8 "os"
9 "time"
10
11 "github.com/octelium/octelium/apis/main/cordiumv1"
12 "github.com/octelium/octelium/octelium-go"
13 "github.com/octelium/octelium/pkg/apiutils/umetav1"
14 "github.com/octelium/octelium/pkg/grpcerr"
15)
16
17func main() {
18 if err := doMain(context.Background()); err != nil {
19 panic(err)
20 }
21}
22
23func doMain(ctx context.Context) error {
24 octeliumC, err := octelium.NewClient(ctx, &octelium.ClientConfig{
25 Domain: "example.com",
26 AuthenticationToken: "AUTH_TOKEN",
27 })
28 if err != nil {
29 return err
30 }
31
32 defer octeliumC.Close()
33
34 grpcConn, err := octeliumC.GRPC().GetConn(ctx)
35 if err != nil {
36 return err
37 }
38 c := cordiumv1.NewMainServiceClient(grpcConn)
39
40 workspaceC := cordiumv1.NewWorkspaceServiceClient(grpcConn)
41
42 {
43 spaceList, err := c.ListSpace(ctx, &cordiumv1.ListSpaceOptions{})
44 if err != nil {
45 return err
46 }
47
48 for _, itm := range spaceList.Items {
49 fmt.Printf("Space: %s\n", itm.Metadata.Name)
50 }
51 }
52
53 {
54 workspaceList, err := c.ListWorkspace(ctx, &cordiumv1.ListWorkspaceOptions{})
55 if err != nil {
56 return err
57 }
58
59 for _, itm := range workspaceList.Items {
60 fmt.Printf("Workspace: %s\n", itm.Metadata.Name)
61 }
62 }
63 {
64 ws, err := c.CreateWorkspace(ctx, &cordiumv1.Workspace{
65 Spec: &cordiumv1.Workspace_Spec{
66 Image: &cordiumv1.Workspace_Spec_Image{
67 Type: &cordiumv1.Workspace_Spec_Image_Registry_{
68 Registry: &cordiumv1.Workspace_Spec_Image_Registry{
69 Url: "node:22-bookworm",
70 },
71 },
72 },
73
74 Repository: &cordiumv1.Workspace_Spec_Repository{
75 Url: "https://github.com/myorg/orders-api",
76 },
77
78 Runtime: &cordiumv1.Workspace_Spec_Runtime{
79 EnvVars: []*cordiumv1.Workspace_Spec_Runtime_EnvVar{
80 {
81 Key: "NODE_ENV",
82 Type: &cordiumv1.Workspace_Spec_Runtime_EnvVar_Value{
83 Value: "development",
84 },
85 },
86 },
87
88 Tasks: []*cordiumv1.Workspace_Spec_Runtime_Task{
89 {
90 Name: "install",
91 Type: cordiumv1.Workspace_Spec_Runtime_Task_ON_CREATE,
92 Run: `
93npm install
94`,
95 WorkingDir: "/workspace/repo",
96 },
97
98 {
99 Name: "start-api",
100 Type: cordiumv1.Workspace_Spec_Runtime_Task_POST_START,
101 Run: `
102npm run dev
103`,
104 WorkingDir: "/workspace/repo",
105 IsBackground: true,
106 },
107 },
108 },
109
110 Limit: &cordiumv1.Workspace_Spec_Limit{
111 Cpu: &cordiumv1.Workspace_Spec_Limit_CPU{
112 Millicores: 4000,
113 },
114 Memory: &cordiumv1.Workspace_Spec_Limit_Memory{
115 Megabytes: 8192,
116 },
117 },
118 },
119 })
120 if err != nil {
121 return err
122 }
123
124 ws.Spec.Limit.Storage = &cordiumv1.Workspace_Spec_Limit_Storage{
125 Megabytes: 20000,
126 }
127
128 ws, err = c.UpdateWorkspace(ctx, ws)
129 if err != nil {
130 return err
131 }
132
133 _, err = c.StartWorkspace(ctx, &cordiumv1.StartWorkspaceRequest{
134 WorkspaceRef: umetav1.GetObjectReference(ws),
135 })
136 if err != nil {
137 return err
138 }
139
140 watcher, err := c.WatchWorkspace(ctx, &cordiumv1.WatchWorkspaceRequest{
141 WorkspaceRef: umetav1.GetObjectReference(ws),
142 })
143 if err != nil {
144 return err
145 }
146
147 if err := func() error {
148 for {
149 msg, err := watcher.Recv()
150 if err != nil {
151 return err
152 }
153
154 switch msg.Type.(type) {
155 case *cordiumv1.WatchWorkspaceResponse_Update_:
156 cur := msg.GetUpdate().NewItem
157 old := msg.GetUpdate().OldItem
158
159 if cur.Status.State == old.Status.State {
160 continue
161 }
162
163 switch cur.Status.State {
164 case cordiumv1.Workspace_Status_PREPARING, cordiumv1.Workspace_Status_RUNNING:
165 return nil
166 }
167 }
168 }
169 }(); err != nil {
170 return err
171 }
172
173 execStream, err := workspaceC.Exec(ctx)
174 if err != nil {
175 return err
176 }
177
178 if err := execStream.Send(&cordiumv1.ExecRequest{
179 Type: &cordiumv1.ExecRequest_Request_{
180 Request: &cordiumv1.ExecRequest_Request{
181 WorkspaceRef: umetav1.GetObjectReference(ws),
182 Command: "ls -la",
183 WorkingDir: "/",
184 },
185 },
186 }); err != nil {
187 return err
188 }
189
190 if err := func() error {
191 for {
192 select {
193 case <-ctx.Done():
194
195 return nil
196 default:
197 msg, err := execStream.Recv()
198 if err != nil {
199 if errors.Is(err, io.EOF) || grpcerr.IsCanceled(err) {
200 return nil
201 }
202 time.Sleep(200 * time.Millisecond)
203 continue
204 }
205
206 switch msg.Type.(type) {
207 case *cordiumv1.ExecResponse_Exit_:
208 if msg.GetExit().Code != 0 {
209 os.Exit(int(msg.GetExit().Code))
210 }
211 return nil
212 case *cordiumv1.ExecResponse_Stdout_:
213 if _, err := os.Stdout.Write(msg.GetStdout().Data); err != nil {
214 } else {
215 os.Stdout.Write([]byte("\n"))
216 }
217 case *cordiumv1.ExecResponse_Stderr_:
218 if _, err := os.Stderr.Write(msg.GetStderr().Data); err != nil {
219 } else {
220 os.Stderr.Write([]byte("\n"))
221 }
222 }
223 }
224 }
225
226 return nil
227
228 }(); err != nil {
229 return err
230 }
231
232 _, err = c.StopWorkspace(ctx, &cordiumv1.StopWorkspaceRequest{
233 WorkspaceRef: umetav1.GetObjectReference(ws),
234 })
235 if err != nil {
236 return err
237 }
238 }
239
240 return nil
241}Here is the same example written in Python:
1import asyncio
2import sys
3from typing import AsyncIterator
4
5import betterproto
6from octelium.api.main.cordium.v1 import (
7 ExecRequest,
8 ExecRequestRequest,
9 ListSpaceOptions,
10 ListWorkspaceOptions,
11 MainServiceStub,
12 StartWorkspaceRequest,
13 StopWorkspaceRequest,
14 WatchWorkspaceRequest,
15 Workspace,
16 WorkspaceServiceStub,
17 WorkspaceSpec,
18 WorkspaceSpecImage,
19 WorkspaceSpecImageRegistry,
20 WorkspaceSpecLimit,
21 WorkspaceSpecLimitCpu,
22 WorkspaceSpecLimitMemory,
23 WorkspaceSpecLimitStorage,
24 WorkspaceSpecRepository,
25 WorkspaceSpecRuntime,
26 WorkspaceSpecRuntimeEnvVar,
27 WorkspaceSpecRuntimeTask,
28 WorkspaceSpecRuntimeTaskType,
29 WorkspaceStatusState,
30)
31from octelium.api.main.meta.v1 import Metadata, ObjectReference
32from octelium.sdk import AuthConfig, AuthTokenConfig, OcteliumClient, OcteliumClientConfig
33
34
35def get_object_reference(obj: Workspace) -> ObjectReference:
36 return ObjectReference(
37 name=obj.metadata.name,
38 uid=obj.metadata.uid,
39 )
40
41
42async def exec_requests(ws: Workspace) -> AsyncIterator[ExecRequest]:
43 yield ExecRequest(
44 request=ExecRequestRequest(
45 workspace_ref=get_object_reference(ws),
46 command="ls -la",
47 working_dir="/",
48 )
49 )
50
51
52async def main() -> None:
53 async with await OcteliumClient.create(
54 OcteliumClientConfig(
55 domain="example.com",
56 auth=AuthConfig(
57 type="auth_token",
58 auth_token=AuthTokenConfig(
59 token="AUTH_TOKEN"
60 ),
61 ),
62 )
63 ) as client:
64 c = MainServiceStub(client._channel)
65 workspace_c = WorkspaceServiceStub(client._channel)
66
67 space_list = await c.list_space(ListSpaceOptions())
68 for itm in space_list.items:
69 print(f"Space: {itm.metadata.name}")
70
71 workspace_list = await c.list_workspace(ListWorkspaceOptions())
72 for itm in workspace_list.items:
73 print(f"Workspace: {itm.metadata.name}")
74
75 ws = await c.create_workspace(
76 Workspace(
77 metadata=Metadata(
78 name="my-workspace",
79 ),
80 spec=WorkspaceSpec(
81 image=WorkspaceSpecImage(
82 registry=WorkspaceSpecImageRegistry(
83 url="node:22-bookworm",
84 ),
85 ),
86 repository=WorkspaceSpecRepository(
87 url="https://github.com/myorg/orders-api",
88 ),
89 runtime=WorkspaceSpecRuntime(
90 env_vars=[
91 WorkspaceSpecRuntimeEnvVar(
92 key="NODE_ENV",
93 value="development",
94 ),
95 ],
96 tasks=[
97 WorkspaceSpecRuntimeTask(
98 name="install",
99 type=WorkspaceSpecRuntimeTaskType.ON_CREATE,
100 run="npm install\n",
101 working_dir="/workspace/repo",
102 ),
103 WorkspaceSpecRuntimeTask(
104 name="start-api",
105 type=WorkspaceSpecRuntimeTaskType.POST_START,
106 run="npm run dev\n",
107 working_dir="/workspace/repo",
108 is_background=True,
109 ),
110 ],
111 ),
112 limit=WorkspaceSpecLimit(
113 cpu=WorkspaceSpecLimitCpu(millicores=4000),
114 memory=WorkspaceSpecLimitMemory(megabytes=8192),
115 ),
116 ),
117 )
118 )
119
120 ws.spec.limit.storage = WorkspaceSpecLimitStorage(megabytes=20000)
121 ws = await c.update_workspace(ws)
122
123 await c.start_workspace(
124 StartWorkspaceRequest(workspace_ref=get_object_reference(ws))
125 )
126
127 async for msg in c.watch_workspace(
128 WatchWorkspaceRequest(workspace_ref=get_object_reference(ws))
129 ):
130 field_name, _ = betterproto.which_one_of(msg, "type")
131 if field_name != "update":
132 continue
133
134 cur = msg.update.new_item
135 old = msg.update.old_item
136
137 if cur.status.state == old.status.state:
138 continue
139
140 if cur.status.state in (
141 WorkspaceStatusState.PREPARING,
142 WorkspaceStatusState.RUNNING,
143 ):
144 break
145
146 async for msg in workspace_c.exec(exec_requests(ws)):
147 field_name, _ = betterproto.which_one_of(msg, "type")
148 if field_name == "exit":
149 if msg.exit.code != 0:
150 sys.exit(msg.exit.code)
151 break
152 elif field_name == "stdout":
153 sys.stdout.buffer.write(msg.stdout.data)
154 sys.stdout.buffer.write(b"\n")
155 sys.stdout.buffer.flush()
156 elif field_name == "stderr":
157 sys.stderr.buffer.write(msg.stderr.data)
158 sys.stderr.buffer.write(b"\n")
159 sys.stderr.buffer.flush()
160
161 await c.stop_workspace(
162 StopWorkspaceRequest(workspace_ref=get_object_reference(ws))
163 )
164
165
166if __name__ == "__main__":
167 asyncio.run(main())