dodo: Support Sketch agent
Change-Id: I4dcd6aab7d7a2c2e86aaf1ad8d36d30a649ab31d
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index e906f43..c26fa77 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -30,6 +30,11 @@
cluster: clusterMap[strings.ToLower(_cluster)]
}
+ geminiApiKey?: string
+ for v in agent {
+ "sketch_\(v.name)_session_id": string @role(sketch-session-id)
+ }
+
for v in _postgresql {
for i, e in v.expose {
"port_postgresql_\(v.name)_\(i)": int @role(port)
@@ -135,26 +140,30 @@
}
// TODO(gio): add value
+// Do not specify both alias and value
#EnvVar: {
name: string
alias?: string
+ value?: string
}
#AppTmpl: {
- name: string | *"app"
- type: string
+ nodeId?: string
+ name: string | *"app"
+ type: string
+ agentMode: bool | *false
ingress?: [...#AppIngress]
expose: [...#PortDomain] | *[]
rootDir: string
runConfiguration: [...#Command]
volume: [...string] | *[]
- dev: #Dev
- vm: #VMCustomization
+ dev: #Dev | *{enabled: false}
+ vm: #VMCustomization
// TODO(gio): check for duplicate values
apiPort: #PortValue | *3000
ports: [...#Port]
env: [...#EnvVar] | *[]
- source: close({
+ source?: close({
repository: string
branch: string | *"master"
rootDir: string | *"/"
@@ -184,6 +193,11 @@
}
},
],
+ [
+ for e in env if e.value != _|_ {
+ "\(strings.ToUpper(e.name))=\(e.value)"
+ },
+ ],
])
...
@@ -467,9 +481,73 @@
#NodeJSApp: #NodeJS2310 | #NodeJS2402
-#App: #GoApp | #HugoApp | #PHPApp | #NextjsApp | #NodeJSApp | #DenoApp
+#SketchApp: #AppTmpl & {
+ agentMode: true
-service: [...#App]
+ name: string
+ _name: name
+
+ type: "sketch:latest"
+
+ geminiApiKey?: string
+ _geminiApiKey: string
+ if geminiApiKey != _|_ {
+ _geminiApiKey: geminiApiKey
+ }
+ if geminiApiKey == _|_ && input.geminiApiKey != _|_ {
+ _geminiApiKey: input.geminiApiKey
+ }
+
+ ports: [{
+ name: "agent"
+ value: 2001
+ }, {
+ name: "p8080"
+ value: 8080
+ }, {
+ name: "p8081"
+ value: 8081
+ }, {
+ name: "p8082"
+ value: 8082
+ }, {
+ name: "p8083"
+ value: 8083
+ }, {
+ name: "p8084"
+ value: 8084
+ }]
+ env: [{
+ name: "DODO_PROJECT_ID"
+ value: input.appId
+ }, {
+ name: "DODO_API_BASE_ADDR"
+ value: input.managerAddr
+ }, {
+ name: "GEMINI_API_KEY"
+ value: _geminiApiKey
+ }]
+ rootDir: "/dodo/volume/\(_name)-apps"
+
+ lastCmdEnv: [...string]
+
+ _sessionId: input["sketch_\(name)_session_id"]
+
+ runConfiguration: [{
+ bin: "sketch -verbose -unsafe -skaband-addr=\"\" -addr=\"0.0.0.0:2001\" -model=gemini -session-id=\"\(_sessionId)\""
+ env: lastCmdEnv
+ }]
+}
+
+#AgentApp: #SketchApp
+
+#NonAgentApp: #GoApp | #HugoApp | #PHPApp | #NextjsApp | #NodeJSApp | #DenoApp
+
+#App: #NonAgentApp | #AgentApp
+
+agent: [...#AgentApp]
+
+service: [...#NonAgentApp]
_serviceDevEnabled: {
images: {}
@@ -617,11 +695,19 @@
containerPort: p.value
protocol: p.protocol
}]
- appDir: svc.rootDir
- appId: input.appId
- repoAddr: svc.source.repository
- branch: svc.source.branch
- rootDir: svc.source.rootDir
+ appDir: svc.rootDir
+ appId: input.appId
+ if svc.source != _|_ {
+ repoAddr: svc.source.repository
+ branch: svc.source.branch
+ rootDir: svc.source.rootDir
+ }
+ if svc.source == _|_ {
+ repoAddr: ""
+ branch: ""
+ rootDir: ""
+ }
+ agentMode: svc.agentMode
sshPrivateKey: base64.Encode(null, input.key.private)
runCfg: base64.Encode(null, json.Marshal(svc.runConfiguration))
managerAddr: input.managerAddr
@@ -730,8 +816,9 @@
}]
runCmd: list.Concat([[
["sh", "-c", "chown \(username):\(username) /home/\(username)/.cache"],
- ["sh", "-c", "GIT_SSH_COMMAND='ssh -i /home/\(username)/.ssh/key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new' git clone --branch \(svc.source.branch) \(svc.source.repository) /home/\(username)/code"],
- ["sh", "-c", "chown -R \(username):\(username) /home/\(username)/code"],
+ if svc.source != _|_ {
+ ["sh", "-c", "GIT_SSH_COMMAND='ssh -i /home/\(username)/.ssh/key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new' git clone --branch \(svc.source.branch) \(svc.source.repository) /home/\(username)/code"]
+ },
["sh", "-c", "chown -R \(username):\(username) /home/\(username)"],
], svc.vm.cloudInit.runCmd])
}
@@ -820,6 +907,12 @@
for v in _volume {
"\(v.name)": v
}
+ for v in agent {
+ "\(v.name)-apps": {
+ name: "\(v.name)-apps"
+ size: "1Gi"
+ }
+ }
}
postgresql: {
for v in _postgresql {
@@ -842,6 +935,15 @@
svc: v
}
}
+ for v in agent {
+ "\(v.name)": #Service & {
+ name: v.name
+ svc: v & {
+ dev: enabled: false
+ volume: ["\(v.name)-apps"]
+ }
+ }
+ }
}
}
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 0965507..eee4841 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -1298,6 +1298,8 @@
return []string{}
case KindPassword:
return []string{}
+ case KindSketchSessionId:
+ return []string{}
default:
panic("MUST NOT REACH!")
}
diff --git a/core/installer/derived.go b/core/installer/derived.go
index 4030623..eebb8ad 100644
--- a/core/installer/derived.go
+++ b/core/installer/derived.go
@@ -3,8 +3,10 @@
import (
"fmt"
"html/template"
+ "math/rand/v2"
"strings"
+ "github.com/richardlehane/crock32"
"github.com/sethvargo/go-password/password"
)
@@ -101,6 +103,9 @@
}
ret[k] = psswd
}
+ if def.Kind() == KindSketchSessionId {
+ ret[k] = GenerateSketchSessionId()
+ }
if def.Kind() == KindVPNAuthKey {
enabled := true
if v, ok := def.Meta()["enabledField"]; ok {
@@ -146,6 +151,8 @@
ret[k] = v
case KindPassword:
ret[k] = v
+ case KindSketchSessionId:
+ ret[k] = v
case KindArrayString:
a, ok := v.([]string)
if !ok {
@@ -352,3 +359,12 @@
func GeneratePassword() (string, error) {
return password.Generate(20, 5, 0, false, true)
}
+
+func GenerateSketchSessionId() string {
+ u1, u2 := rand.Uint64(), rand.Uint64N(1<<16)
+ s := crock32.Encode(u1) + crock32.Encode(uint64(u2))
+ if len(s) < 16 {
+ s += strings.Repeat("0", 16-len(s))
+ }
+ return s[0:4] + "-" + s[4:8] + "-" + s[8:12] + "-" + s[12:16]
+}
diff --git a/core/installer/dodo_app_test.go b/core/installer/dodo_app_test.go
index 22835b6..f81f2f9 100644
--- a/core/installer/dodo_app_test.go
+++ b/core/installer/dodo_app_test.go
@@ -642,3 +642,78 @@
}
t.Log(string(access))
}
+
+const sketch = `
+{
+ "agent": [
+ {
+ "type": "sketch:latest",
+ "name": "dev",
+ "geminiApiKey": "foo",
+ }
+ ],
+}
+`
+
+func TestSketch(t *testing.T) {
+ app, err := NewDodoApp([]byte(sketch))
+ if err != nil {
+ for _, e := range errors.Errors(err) {
+ t.Log(e)
+ }
+ t.Fatal(err)
+ }
+ release := Release{
+ Namespace: "foo",
+ AppInstanceId: "foo-bar",
+ RepoAddr: "ssh://192.168.100.210:22/config",
+ AppDir: "/foo/bar",
+ }
+ keyGen := testKeyGen{}
+ r, err := app.Render(release, env, networks, nil, map[string]any{
+ "managerAddr": "",
+ "appId": "",
+ "geminiApiKey": "dev",
+ }, nil, keyGen)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Log(string(r.Raw))
+}
+
+const sketchGlobalGeminiApiKey = `
+{
+ "agent": [
+ {
+ "type": "sketch:latest",
+ "name": "dev",
+ }
+ ],
+}
+`
+
+func TestSketchGlobalGeminiApiKey(t *testing.T) {
+ app, err := NewDodoApp([]byte(sketchGlobalGeminiApiKey))
+ if err != nil {
+ for _, e := range errors.Errors(err) {
+ t.Log(e)
+ }
+ t.Fatal(err)
+ }
+ release := Release{
+ Namespace: "foo",
+ AppInstanceId: "foo-bar",
+ RepoAddr: "ssh://192.168.100.210:22/config",
+ AppDir: "/foo/bar",
+ }
+ keyGen := testKeyGen{}
+ r, err := app.Render(release, env, networks, nil, map[string]any{
+ "managerAddr": "",
+ "appId": "",
+ "geminiApiKey": "dev",
+ }, nil, keyGen)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t.Log(string(r.Raw))
+}
diff --git a/core/installer/go.mod b/core/installer/go.mod
index b183a46..eb35fb5 100644
--- a/core/installer/go.mod
+++ b/core/installer/go.mod
@@ -19,6 +19,7 @@
github.com/libdns/libdns v0.2.2
github.com/miekg/dns v1.1.58
github.com/ncruces/go-sqlite3 v0.17.0
+ github.com/richardlehane/crock32 v1.0.1
github.com/sethvargo/go-password v0.3.1
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.32.0
diff --git a/core/installer/go.sum b/core/installer/go.sum
index 1da1bc6..a6448e0 100644
--- a/core/installer/go.sum
+++ b/core/installer/go.sum
@@ -363,6 +363,8 @@
github.com/prometheus/procfs v0.13.0/go.mod h1:cd4PFCR54QLnGKPaKGA6l+cfuNXtht43ZKY6tow0Y1g=
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d h1:HWfigq7lB31IeJL8iy7jkUmU/PG1Sr8jVGhS749dbUA=
github.com/protocolbuffers/txtpbfmt v0.0.0-20241112170944-20d2c9ebc01d/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c=
+github.com/richardlehane/crock32 v1.0.1 h1:GV9EqtAr7RminQ8oGrDt3gYXkzDDPJ5fROaO1Mux14g=
+github.com/richardlehane/crock32 v1.0.1/go.mod h1:xUIlLABtHBgs1bNIBdUQR9F2xtRzS0TujtbR68hmEWU=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
diff --git a/core/installer/samples/canvas.rest b/core/installer/samples/canvas.rest
index 1ff7356..42bc95d 100644
--- a/core/installer/samples/canvas.rest
+++ b/core/installer/samples/canvas.rest
@@ -1,54 +1,24 @@
-PUT http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app/dodo-app-gry
+POST http://appmanager.hgrz-appmanager.svc.cluster.local/api/dodo-app
Content-Type: application/json
{
"config": {
- "service": [
+ "input": {
+ "sketch_dev_gemini_api_key": "AIzaSyAx_vF0HJyT55A09iXtjPhf2JocNOGaWCo"
+ },
+ "agent": [
{
- "dev": {
- "enabled": false
- },
+ "name": "dev",
"ingress": [
{
- "auth": {
- "enabled": true,
- "noAuthPathPatterns": ["^/api/webhook/github/push$"]
- },
- "network": "public",
+ "network": "private",
"port": {
- "name": "web"
+ "value": 2001
},
- "subdomain": "canvas"
+ "subdomain": "sketch"
}
- ],
- "name": "canvas",
- "ports": [
- {
- "name": "web",
- "value": 8080
- },
- {
- "name": "api",
- "value": 8081
- }
- ],
- "source": {
- "branch": "canvas",
- "repository": "https://code.v1.dodo.cloud/pcloud",
- "rootDir": "apps/canvas/back"
- },
- "type": "nodejs:24.0.2",
- "volume": ["data"],
- "preBuildCommands": [{
- "bin": "cd ../front && npm install && npm run build"
- }, {
- "bin": "npx prisma migrate dev"
- }]
+ ]
}
- ],
- "volume": [{
- "name": "data",
- "size": "1Gi"
- }]
+ ]
}
}
diff --git a/core/installer/schema.go b/core/installer/schema.go
index 2b150a6..e575aa5 100644
--- a/core/installer/schema.go
+++ b/core/installer/schema.go
@@ -12,20 +12,21 @@
type Kind int
const (
- KindBoolean Kind = 0
- KindInt = 7
- KindString = 1
- KindStruct = 2
- KindNetwork = 3
- KindMultiNetwork = 10
- KindAuth = 5
- KindSSHKey = 6
- KindNumber = 4
- KindArrayString = 8
- KindPort = 9
- KindVPNAuthKey = 11
- KindCluster = 12
- KindPassword = 13
+ KindBoolean Kind = 0
+ KindInt = 7
+ KindString = 1
+ KindStruct = 2
+ KindNetwork = 3
+ KindMultiNetwork = 10
+ KindAuth = 5
+ KindSSHKey = 6
+ KindNumber = 4
+ KindArrayString = 8
+ KindPort = 9
+ KindVPNAuthKey = 11
+ KindCluster = 12
+ KindPassword = 13
+ KindSketchSessionId = 14
)
type Field struct {
@@ -309,6 +310,8 @@
if role == "password" {
// TODO(gio): implement configurable requirements such as min-length, ...
return basicSchema{name, KindPassword, false, nil}, nil
+ } else if role == "sketch-session-id" {
+ return basicSchema{name, KindSketchSessionId, false, nil}, nil
} else if role == "vpnauthkey" {
meta := map[string]string{}
usernameFieldAttr := v.Attribute("usernameField")