DodoApp: VM optionally takes ssh key as an input

Change-Id: I1557dff32a622762c42fa7947723caa5d810d1ed
diff --git a/apps/app-runner/Makefile b/apps/app-runner/Makefile
index 356ce28..f6a0b86 100644
--- a/apps/app-runner/Makefile
+++ b/apps/app-runner/Makefile
@@ -150,4 +150,4 @@
 
 
 # all
-push: push_golang_1_22_0 push_golang_1_20_0 push_hugo push_php_8_2_apache push_nextjs_deno_2_0_0 push_nodejs_23_1_0 push_deno_2_0_0
+push: push_golang_1_22_0 push_golang_1_20_0 push_hugo push_php_8_2_apache push_nextjs_deno_2_0_0 push_nodejs_23_1_0 push_deno_2_2_0
diff --git a/core/installer/Makefile b/core/installer/Makefile
index fe61694..1655ac0 100644
--- a/core/installer/Makefile
+++ b/core/installer/Makefile
@@ -14,6 +14,9 @@
 	$(podman) build --file=Dockerfile.flux --tag=$(repo_name)/flux:latest . --platform=linux/arm64
 	docker push $(repo_name)/flux:latest
 
+format:
+	go run cuelang.org/go/cmd/cue fmt app_configs/*.cue
+
 build: export CGO_ENABLED=0
 build: clean
 	/usr/local/go/bin/go build -o pcloud cmd/*.go
diff --git a/core/installer/app_configs/app_base.cue b/core/installer/app_configs/app_base.cue
index d43cf0f..d4ee363 100644
--- a/core/installer/app_configs/app_base.cue
+++ b/core/installer/app_configs/app_base.cue
@@ -292,6 +292,7 @@
 	codeServer: #CodeServer | *{enabled: false}
 	cpuCores: int
 	memory:   string
+	sshKey?:  #SSHKey
 	sshKnownHosts: [...string] | *[]
 	sshAuthorizedKeys: [...string] | *[]
 	cloudInit: #CloudInit
@@ -438,6 +439,7 @@
 				hostname:     _name
 				ssh_pwauth:   true
 				disable_root: false
+				// TODO(gio): get keys from memberships service
 				ssh_authorized_keys: list.Concat([[
 					"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOa7FUrmXzdY3no8qNGUk7OPaRcIUi8G7MVbLlff9eB/ lekva@gl-mbp-m1-max.local",
 				], sshAuthorizedKeys])
@@ -457,16 +459,39 @@
 					"""
 					owner:       "\(username):\(username)"
 					permissions: "0644"
-				}], cloudInit.writeFiles])
+				}],
+					if sshKey != _|_ {
+						[{
+							encoding:    "b64"
+							path:        "/home/\(username)/.ssh/key"
+							content:     base64.Encode(null, sshKey.private)
+							owner:       "\(username):\(username)"
+							permissions: "0600"
+							defer:       true
+						}, {
+							encoding:    "b64"
+							path:        "/home/\(username)/.ssh/key.pub"
+							content:     base64.Encode(null, sshKey.public)
+							owner:       "\(username):\(username)"
+							permissions: "0644"
+							defer:       true
+						},
+						]
+					},
+					cloudInit.writeFiles])
 				runcmd: list.Concat([
 					[
 						["sh", "-c", "chown -R \(username):\(username) /home/\(username)"],
-						["sh", "-c", "ssh-keygen -t ed25519 -f /home/\(username)/.ssh/id_ed25519 -q -N ''"],
-						["sh", "-c", "chown \(username):\(username) /home/\(username)/.ssh/id_ed25519*"],
-						["sh", "-c", "chmod 0600 /home/\(username)/.ssh/id_ed25519*"],
+						if sshKey == _|_ {
+							["sh", "-c", "ssh-keygen -t ed25519 -f /home/\(username)/.ssh/key -q -N ''"]
+						},
+						["sh", "-c", "chown \(username):\(username) /home/\(username)/.ssh/*"],
+						["sh", "-c", "chmod 0600 /home/\(username)/.ssh/*"],
 						// TODO(gio): implement post app delete webhook to remove ssh key from memberships
 						// TODO(gio): make memberships-api addr configurable
-						["sh", "-c", "PUBKEY=$(cat /home/\(username)/.ssh/id_ed25519.pub) && curl --request POST --data \"{\\\"user\\\":\\\"\(username)\\\",\\\"publicKey\\\":\\\"${PUBKEY}\\\"}\" http://memberships-api.\(global.namespacePrefix)core-auth-memberships.svc.cluster.local/api/users/\(username)/keys"],
+						if sshKey == _|_ {
+							["sh", "-c", "PUBKEY=$(cat /home/\(username)/.ssh/key.pub) && curl --request POST --data \"{\\\"user\\\":\\\"\(username)\\\",\\\"publicKey\\\":\\\"${PUBKEY}\\\"}\" http://memberships-api.\(global.namespacePrefix)core-auth-memberships.svc.cluster.local/api/users/\(username)/keys"]
+						},
 					],
 					_vpnCmd,
 					_codeServerCmd,
diff --git a/core/installer/app_configs/dodo_app.cue b/core/installer/app_configs/dodo_app.cue
index a02ac1e..3fb4a1b 100644
--- a/core/installer/app_configs/dodo_app.cue
+++ b/core/installer/app_configs/dodo_app.cue
@@ -694,6 +694,7 @@
 			cpuCores: 2
 			memory:   "3Gi"
 			ports:    svc.ports
+			sshKey:   input.key
 			configFiles: {
 				"env.sh": _envProfile
 			}
@@ -706,7 +707,7 @@
 				}]
 				runCmd: list.Concat([[
 					["sh", "-c", "chown \(username):\(username) /home/\(username)/.cache"],
-					["sh", "-c", "GIT_SSH_COMMAND='ssh -i /home/\(username)/.ssh/id_ed25519 -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new' git clone --branch \(svc.source.branch) \(svc.source.repository) /home/\(username)/code"],
+					["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"],
 					["sh", "-c", "chown -R \(username):\(username) /home/\(username)"],
 				], svc.vm.cloudInit.runCmd])
diff --git a/core/installer/dodo_app_test.go b/core/installer/dodo_app_test.go
index 579d3b2..cc92d6b 100644
--- a/core/installer/dodo_app_test.go
+++ b/core/installer/dodo_app_test.go
@@ -525,3 +525,94 @@
 	}
 	t.Log(string(r.Raw))
 }
+
+const foo = `
+{
+  "service": [
+    {
+      "type": "deno:2.2.0",
+      "name": "qwe",
+      "source": {
+        "repository": "git@github.com:giolekva/dodo-blog.git"
+      },
+      "ports": [
+        {
+          "name": "web",
+          "value": 8080,
+          "protocol": "TCP"
+        }
+      ],
+      "env": [
+        {
+          "name": "DODO_POSTGRESQL_DB_URL"
+        },
+        {
+          "name": "DODO_PORT_WEB"
+        }
+      ],
+      "ingress": [
+        {
+          "network": "Private",
+          "subdomain": "blog",
+          "port": {
+            "name": "web"
+          },
+          "auth": {
+            "enabled": false
+          }
+        }
+      ],
+      "preBuildCommands": [],
+      "dev": {
+        "enabled": true,
+        "username": "gio",
+        "codeServer": {
+          "network": "Private",
+          "subdomain": "code"
+        },
+        "ssh": {
+          "network": "Public",
+          "subdomain": "code"
+        }
+      }
+    }
+  ],
+  "volume": [],
+  "postgresql": [
+    {
+      "name": "db",
+      "size": "1Gi",
+      "expose": []
+    }
+  ],
+  "mongodb": []
+}
+`
+
+func TestFoo(t *testing.T) {
+	app, err := NewDodoApp([]byte(foo))
+	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":                "",
+		"sshPrivateKey":        "",
+		"port_service_qwe_ssh": 12,
+		"port_service_qwe_0":   13,
+	}, nil, keyGen)
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Log(string(r.Raw))
+}