VirtualMachine: Implement virtual machines using KubeVirt
Auto adds new VM into given user's Tailscale network
Change-Id: I16847a0b9eacc17b0e794d3b4913eb1d80a93f0a
diff --git a/core/installer/Makefile b/core/installer/Makefile
index 296e780..abdd656 100644
--- a/core/installer/Makefile
+++ b/core/installer/Makefile
@@ -32,7 +32,7 @@
./pcloud --kubeconfig=../../priv/kubeconfig create-env --admin-priv-key=/Users/lekva/.ssh/id_rsa --name=lekva --ip=192.168.0.211 --admin-username=gio
appmanager:
- ./pcloud --kubeconfig=../../priv/kubeconfig-hetzner appmanager --ssh-key=/Users/lekva/.ssh/id_ed25519 --repo-addr=ssh://10.43.196.174/config --port=9090 # --app-repo-addr=http://localhost:8080
+ ./pcloud --kubeconfig=../../priv/kubeconfig-hetzner appmanager --ssh-key=/Users/lekva/.ssh/id_ed25519 --repo-addr=ssh://10.43.196.174/config --port=9090 --headscale-api-addr=http://10.43.193.154 # --app-repo-addr=http://localhost:8080
dodo-app:
./pcloud --kubeconfig=../../priv/kubeconfig-hetzner dodo-app --ssh-key=/Users/lekva/.ssh/id_ed25519 --repo-addr=ssh://10.43.196.174/test
diff --git a/core/installer/app.go b/core/installer/app.go
index 0c9cf6b..cee8a5f 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -196,7 +196,14 @@
type EnvApp interface {
App
- Render(release Release, env EnvConfig, networks []Network, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (EnvAppRendered, error)
+ Render(
+ release Release,
+ env EnvConfig,
+ networks []Network,
+ values map[string]any,
+ charts map[string]helmv2.HelmChartTemplateSpec,
+ vpnKeyGen VPNAuthKeyGenerator,
+ ) (EnvAppRendered, error)
}
type cueApp struct {
@@ -452,8 +459,9 @@
networks []Network,
values map[string]any,
charts map[string]helmv2.HelmChartTemplateSpec,
+ vpnKeyGen VPNAuthKeyGenerator,
) (EnvAppRendered, error) {
- derived, err := deriveValues(values, a.Schema(), networks)
+ derived, err := deriveValues(values, values, a.Schema(), networks, vpnKeyGen)
if err != nil {
return EnvAppRendered{}, err
}
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index c27b641..3157a45 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -35,6 +35,7 @@
nsc NamespaceCreator
jc JobCreator
hf HelmFetcher
+ vpnKeyGen VPNAuthKeyGenerator
appDirRoot string
}
@@ -43,6 +44,7 @@
nsc NamespaceCreator,
jc JobCreator,
hf HelmFetcher,
+ vpnKeyGen VPNAuthKeyGenerator,
appDirRoot string,
) (*AppManager, error) {
return &AppManager{
@@ -51,6 +53,7 @@
nsc,
jc,
hf,
+ vpnKeyGen,
appDirRoot,
}, nil
}
@@ -457,7 +460,7 @@
RepoAddr: m.repoIO.FullAddress(),
AppDir: appDir,
}
- rendered, err := app.Render(release, env, networks, values, nil)
+ rendered, err := app.Render(release, env, networks, values, nil, m.vpnKeyGen)
if err != nil {
return ReleaseResources{}, err
}
@@ -489,7 +492,7 @@
if o.FetchContainerImages {
release.ImageRegistry = imageRegistry
}
- rendered, err = app.Render(release, env, networks, values, localCharts)
+ rendered, err = app.Render(release, env, networks, values, localCharts, m.vpnKeyGen)
if err != nil {
return ReleaseResources{}, err
}
@@ -582,7 +585,7 @@
if err != nil {
return ReleaseResources{}, err
}
- rendered, err := app.Render(config.Release, env, networks, values, renderedCfg.LocalCharts)
+ rendered, err := app.Render(config.Release, env, networks, values, renderedCfg.LocalCharts, m.vpnKeyGen)
if err != nil {
return ReleaseResources{}, err
}
@@ -989,6 +992,8 @@
return []string{}
case KindPort:
return []string{""}
+ case KindVPNAuthKey:
+ return []string{}
default:
panic("MUST NOT REACH!")
}
diff --git a/core/installer/app_repository.go b/core/installer/app_repository.go
index 381a84f..4ce7c9d 100644
--- a/core/installer/app_repository.go
+++ b/core/installer/app_repository.go
@@ -20,6 +20,7 @@
var storeEnvAppConfigs = []string{
"values-tmpl/dodo-app.cue",
+ "values-tmpl/virtual-machine.cue",
"values-tmpl/coder.cue",
"values-tmpl/url-shortener.cue",
"values-tmpl/matrix.cue",
diff --git a/core/installer/app_test.go b/core/installer/app_test.go
index bf0ceac..f0f0e64 100644
--- a/core/installer/app_test.go
+++ b/core/installer/app_test.go
@@ -82,7 +82,7 @@
"groups": "a,b",
},
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -112,7 +112,7 @@
"enabled": false,
},
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -138,7 +138,7 @@
"network": "Public",
"authGroups": "foo,bar",
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -168,7 +168,7 @@
},
"sshPort": 22,
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -193,7 +193,7 @@
"subdomain": "jenkins",
"network": "Private",
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -252,7 +252,7 @@
},
"sshPrivateKey": "private",
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -285,7 +285,7 @@
"groups": "a,b",
},
}
- rendered, err := app.Render(release, env, networks, values, nil)
+ rendered, err := app.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -360,7 +360,7 @@
"managerAddr": "",
"appId": "",
"sshPrivateKey": "",
- }, nil)
+ }, nil, nil)
if err != nil {
t.Fatal(err)
}
@@ -383,7 +383,7 @@
"repoHost": "",
"gitRepoPublicKey": "",
}
- rendered, err := a.Render(release, env, networks, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil, nil)
if err != nil {
t.Fatal(err)
}
diff --git a/core/installer/cmd/app_manager.go b/core/installer/cmd/app_manager.go
index c03c83e..a6224a0 100644
--- a/core/installer/cmd/app_manager.go
+++ b/core/installer/cmd/app_manager.go
@@ -16,10 +16,11 @@
)
var appManagerFlags struct {
- sshKey string
- repoAddr string
- port int
- appRepoAddr string
+ sshKey string
+ repoAddr string
+ port int
+ appRepoAddr string
+ headscaleAPIAddr string
}
func appManagerCmd() *cobra.Command {
@@ -51,6 +52,12 @@
"",
"",
)
+ cmd.Flags().StringVar(
+ &appManagerFlags.headscaleAPIAddr,
+ "headscale-api-addr",
+ "",
+ "",
+ )
return cmd
}
@@ -85,7 +92,8 @@
return err
}
hf := installer.NewGitHelmFetcher()
- m, err := installer.NewAppManager(repoIO, nsc, jc, hf, "/apps")
+ vpnKeyGen := installer.NewHeadscaleAPIClient(appManagerFlags.headscaleAPIAddr)
+ m, err := installer.NewAppManager(repoIO, nsc, jc, hf, vpnKeyGen, "/apps")
if err != nil {
return err
}
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
index f61ee78..2fe1697 100644
--- a/core/installer/cmd/dodo_app.go
+++ b/core/installer/cmd/dodo_app.go
@@ -32,6 +32,7 @@
db string
networks []string
fetchUsersAddr string
+ headscaleAPIAddr string
}
func dodoAppCmd() *cobra.Command {
@@ -123,6 +124,12 @@
[]string{},
"",
)
+ cmd.Flags().StringVar(
+ &dodoAppFlags.headscaleAPIAddr,
+ "headscale-api-addr",
+ "",
+ "",
+ )
return cmd
}
@@ -193,6 +200,7 @@
// &tasks.KustomizationReconciler{},
},
}
+ vpnKeyGen := installer.NewHeadscaleAPIClient(dodoAppFlags.headscaleAPIAddr)
s, err := welcome.NewDodoAppServer(
st,
nf,
@@ -208,6 +216,7 @@
dodoAppFlags.envAppManagerAddr,
nsc,
jc,
+ vpnKeyGen,
env,
dodoAppFlags.external,
dodoAppFlags.fetchUsersAddr,
diff --git a/core/installer/cmd/launcher.go b/core/installer/cmd/launcher.go
index 0af1d1e..e671fe6 100644
--- a/core/installer/cmd/launcher.go
+++ b/core/installer/cmd/launcher.go
@@ -74,7 +74,7 @@
if err != nil {
return err
}
- appManager, err := installer.NewAppManager(repoIO, nil, nil, nil, "/apps")
+ appManager, err := installer.NewAppManager(repoIO, nil, nil, nil, nil, "/apps")
if err != nil {
return err
}
diff --git a/core/installer/cmd/rewrite.go b/core/installer/cmd/rewrite.go
index 3ebb390..0562c0e 100644
--- a/core/installer/cmd/rewrite.go
+++ b/core/installer/cmd/rewrite.go
@@ -62,7 +62,7 @@
log.Println("Creating repository")
r := installer.NewInMemoryAppRepository(installer.CreateAllApps())
hf := installer.NewGitHelmFetcher()
- mgr, err := installer.NewAppManager(repoIO, nil, nil, hf, "/apps")
+ mgr, err := installer.NewAppManager(repoIO, nil, nil, hf, nil, "/apps")
if err != nil {
return err
}
diff --git a/core/installer/derived.go b/core/installer/derived.go
index 7a5e5f3..aabda98 100644
--- a/core/installer/derived.go
+++ b/core/installer/derived.go
@@ -3,6 +3,7 @@
import (
"fmt"
"html/template"
+ "strings"
)
type Release struct {
@@ -55,7 +56,21 @@
return ret
}
-func deriveValues(values any, schema Schema, networks []Network) (map[string]any, error) {
+func getField(v any, f string) any {
+ for _, i := range strings.Split(f, ".") {
+ vm := v.(map[string]any)
+ v = vm[i]
+ }
+ return v
+}
+
+func deriveValues(
+ root any,
+ values any,
+ schema Schema,
+ networks []Network,
+ vpnKeyGen VPNAuthKeyGenerator,
+) (map[string]any, error) {
ret := make(map[string]any)
for _, f := range schema.Fields() {
k := f.Name
@@ -74,6 +89,16 @@
"private": string(key.RawPrivateKey()),
}
}
+ if def.Kind() == KindVPNAuthKey {
+ usernameField := def.Meta()["usernameField"]
+ // TODO(gio): Improve getField
+ username := getField(root, usernameField)
+ authKey, err := vpnKeyGen.Generate(username.(string))
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = authKey
+ }
continue
}
switch def.Kind() {
@@ -85,6 +110,8 @@
ret[k] = v
case KindPort:
ret[k] = v
+ case KindVPNAuthKey:
+ ret[k] = v
case KindArrayString:
a, ok := v.([]string)
if !ok {
@@ -120,19 +147,19 @@
}
ret[k] = picked
case KindAuth:
- r, err := deriveValues(v, AuthSchema, networks)
+ r, err := deriveValues(v, v, AuthSchema, networks, vpnKeyGen)
if err != nil {
return nil, err
}
ret[k] = r
case KindSSHKey:
- r, err := deriveValues(v, SSHKeySchema, networks)
+ r, err := deriveValues(v, v, SSHKeySchema, networks, vpnKeyGen)
if err != nil {
return nil, err
}
ret[k] = r
case KindStruct:
- r, err := deriveValues(v, def, networks)
+ r, err := deriveValues(v, v, def, networks, vpnKeyGen)
if err != nil {
return nil, err
}
@@ -163,6 +190,8 @@
ret[k] = v
case KindPort:
ret[k] = v
+ case KindVPNAuthKey:
+ ret[k] = v
case KindArrayString:
a, ok := v.([]string)
if !ok {
diff --git a/core/installer/derived_test.go b/core/installer/derived_test.go
new file mode 100644
index 0000000..7a34154
--- /dev/null
+++ b/core/installer/derived_test.go
@@ -0,0 +1,34 @@
+package installer
+
+import (
+ "testing"
+)
+
+type testKeyGen struct{}
+
+func (g testKeyGen) Generate(username string) (string, error) {
+ return username, nil
+}
+
+func TestDeriveVPNAuthKey(t *testing.T) {
+ schema := structSchema{
+ "input",
+ []Field{
+ Field{"username", basicSchema{"username", KindString, false, nil}},
+ Field{"authKey", basicSchema{"authKey", KindVPNAuthKey, false, map[string]string{
+ "usernameField": "username",
+ }}},
+ },
+ false,
+ }
+ input := map[string]any{
+ "username": "foo",
+ }
+ v, err := deriveValues(input, input, schema, nil, testKeyGen{})
+ if err != nil {
+ t.Fatal(err)
+ }
+ if key, ok := v["authKey"].(string); !ok || key != "foo" {
+ t.Fatal(v)
+ }
+}
diff --git a/core/installer/schema.go b/core/installer/schema.go
index 8b49905..b02f3b7 100644
--- a/core/installer/schema.go
+++ b/core/installer/schema.go
@@ -22,6 +22,7 @@
KindNumber = 4
KindArrayString = 8
KindPort = 9
+ KindVPNAuthKey = 11
)
type Field struct {
@@ -34,13 +35,14 @@
Kind() Kind
Fields() []Field
Advanced() bool
+ Meta() map[string]string
}
var AuthSchema Schema = structSchema{
name: "Auth",
fields: []Field{
- Field{"enabled", basicSchema{"Enabled", KindBoolean, false}},
- Field{"groups", basicSchema{"Groups", KindString, false}},
+ Field{"enabled", basicSchema{"Enabled", KindBoolean, false, nil}},
+ Field{"groups", basicSchema{"Groups", KindString, false, nil}},
},
advanced: false,
}
@@ -48,8 +50,8 @@
var SSHKeySchema Schema = structSchema{
name: "SSH Key",
fields: []Field{
- Field{"public", basicSchema{"Public Key", KindString, false}},
- Field{"private", basicSchema{"Private Key", KindString, false}},
+ Field{"public", basicSchema{"Public Key", KindString, false, nil}},
+ Field{"private", basicSchema{"Private Key", KindString, false, nil}},
},
advanced: true,
}
@@ -166,6 +168,7 @@
name string
kind Kind
advanced bool
+ meta map[string]string
}
func (s basicSchema) Name() string {
@@ -184,6 +187,10 @@
return s.advanced
}
+func (s basicSchema) Meta() map[string]string {
+ return s.meta
+}
+
type structSchema struct {
name string
fields []Field
@@ -206,6 +213,10 @@
return s.advanced
}
+func (s structSchema) Meta() map[string]string {
+ return map[string]string{}
+}
+
func NewCueSchema(name string, v cue.Value) (Schema, error) {
nameAttr := v.Attribute("name")
if nameAttr.Err() == nil {
@@ -218,29 +229,36 @@
}
switch v.IncompleteKind() {
case cue.StringKind:
- return basicSchema{name, KindString, false}, nil
+ if role == "vpnauthkey" {
+ meta := map[string]string{}
+ usernameAttr := v.Attribute("usernameField")
+ meta["usernameField"] = strings.ToLower(usernameAttr.Contents())
+ return basicSchema{name, KindVPNAuthKey, true, meta}, nil
+ } else {
+ return basicSchema{name, KindString, false, nil}, nil
+ }
case cue.BoolKind:
- return basicSchema{name, KindBoolean, false}, nil
+ return basicSchema{name, KindBoolean, false, nil}, nil
case cue.NumberKind:
- return basicSchema{name, KindNumber, false}, nil
+ return basicSchema{name, KindNumber, false, nil}, nil
case cue.IntKind:
if role == "port" {
- return basicSchema{name, KindPort, true}, nil
+ return basicSchema{name, KindPort, true, nil}, nil
} else {
- return basicSchema{name, KindInt, false}, nil
+ return basicSchema{name, KindInt, false, nil}, nil
}
case cue.ListKind:
if isMultiNetwork(v) {
- return basicSchema{name, KindMultiNetwork, false}, nil
+ return basicSchema{name, KindMultiNetwork, false, nil}, nil
}
- return basicSchema{name, KindArrayString, false}, nil
+ return basicSchema{name, KindArrayString, false, nil}, nil
case cue.StructKind:
if isNetwork(v) {
- return basicSchema{name, KindNetwork, false}, nil
+ return basicSchema{name, KindNetwork, false, nil}, nil
} else if isAuth(v) {
- return basicSchema{name, KindAuth, false}, nil
+ return basicSchema{name, KindAuth, false, nil}, nil
} else if isSSHKey(v) {
- return basicSchema{name, KindSSHKey, true}, nil
+ return basicSchema{name, KindSSHKey, true, nil}, nil
}
s := structSchema{name, make([]Field, 0), false}
f, err := v.Fields(cue.Schema())
diff --git a/core/installer/schema_test.go b/core/installer/schema_test.go
index cca0bd7..61739fb 100644
--- a/core/installer/schema_test.go
+++ b/core/installer/schema_test.go
@@ -8,17 +8,17 @@
scm := structSchema{
"a",
[]Field{
- Field{"x", basicSchema{"x", KindString, false}},
- Field{"y", basicSchema{"y", KindInt, false}},
- Field{"z", basicSchema{"z", KindPort, false}},
+ Field{"x", basicSchema{"x", KindString, false, nil}},
+ Field{"y", basicSchema{"y", KindInt, false, nil}},
+ Field{"z", basicSchema{"z", KindPort, false, nil}},
Field{
"w",
structSchema{
"w",
[]Field{
- Field{"x", basicSchema{"x", KindString, false}},
- Field{"y", basicSchema{"y", KindInt, false}},
- Field{"z", basicSchema{"z", KindPort, false}},
+ Field{"x", basicSchema{"x", KindString, false, nil}},
+ Field{"y", basicSchema{"y", KindInt, false, nil}},
+ Field{"z", basicSchema{"z", KindPort, false, nil}},
},
false,
},
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index 9d0c011..3682533 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -18,7 +18,7 @@
if err != nil {
return err
}
- appManager, err := installer.NewAppManager(r, st.nsCreator, st.jc, st.hf, "/apps")
+ appManager, err := installer.NewAppManager(r, st.nsCreator, st.jc, st.hf, nil, "/apps")
if err != nil {
return err
}
diff --git a/core/installer/values-tmpl/appmanager.cue b/core/installer/values-tmpl/appmanager.cue
index 75358a3..aad8f52 100644
--- a/core/installer/values-tmpl/appmanager.cue
+++ b/core/installer/values-tmpl/appmanager.cue
@@ -68,6 +68,7 @@
values: {
repoAddr: input.repoAddr
sshPrivateKey: base64.Encode(null, input.sshPrivateKey)
+ headscaleAPIAddr: "http://headscale-api.\(global.namespacePrefix)app-headscale.svc.cluster.local"
ingress: {
className: input.network.ingressClass
domain: _domain
diff --git a/core/installer/values-tmpl/virtual-machine.cue b/core/installer/values-tmpl/virtual-machine.cue
new file mode 100644
index 0000000..292c082
--- /dev/null
+++ b/core/installer/values-tmpl/virtual-machine.cue
@@ -0,0 +1,72 @@
+input: {
+ name: string @name(Hostname)
+ username: string @name(Username)
+ authKey: string @name(Auth Key) @role(VPNAuthKey) @usernameField(username)
+ cpuCores: int | *1 @name(CPU Cores)
+ memory: string | *"2Gi" @name(Memory)
+}
+
+name: "Virutal Machine"
+namespace: "app-vm"
+readme: "Virtual Machine"
+description: "Virtual Machine"
+icon: """
+<svg xmlns="http://www.w3.org/2000/svg" width="50" height="50" viewBox="0 0 2048 2048"><path fill="currentColor" d="M1280 384H640V256h640zm0 1024H640v-128h640zm0 256H640v-128h640zM1408 0q27 0 50 10t40 27t28 41t10 50v1792H384V128q0-27 10-50t27-40t41-28t50-10zm0 128H512v1664h896z"/></svg>"""
+
+charts: {
+ virtualMachine: {
+ kind: "GitRepository"
+ address: "https://code.v1.dodo.cloud/helm-charts"
+ branch: "main"
+ path: "charts/virtual-machine"
+ }
+}
+
+helm: {
+ "virtual-machine": {
+ chart: charts.virtualMachine
+ values: {
+ name: input.name
+ cpuCores: input.cpuCores
+ memory: input.memory
+ disk: {
+ source: "https://cloud.debian.org/images/cloud/bookworm-backports/latest/debian-12-backports-generic-amd64.qcow2"
+ size: "64Gi"
+ }
+ ports: [22, 8080]
+ cloudInit: userData: _cloudInitUserData
+ }
+ }
+}
+
+_cloudInitUserData: {
+ system_info: {
+ default_user: {
+ name: input.username
+ home: "/home/\(input.username)"
+ }
+ }
+ password: "dodo" // TODO(gio): remove if possible
+ chpasswd: {
+ expire: false
+ }
+ hostname: input.name
+ ssh_pwauth: true
+ disable_root: false
+ ssh_authorized_keys: [
+ "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOa7FUrmXzdY3no8qNGUk7OPaRcIUi8G7MVbLlff9eB/ lekva@gl-mbp-m1-max.local"
+ ]
+ runcmd: [
+ ["sh", "-c", "curl -fsSL https://tailscale.com/install.sh | sh"],
+ // TODO(gio): take auth key from input
+ // TODO(gio): enable tailscale ssh
+ ["sh", "-c", "tailscale up --login-server=https://headscale.\(global.domain) --auth-key=\(input.authKey) --accept-routes"],
+ ["sh", "-c", "curl -fsSL https://code-server.dev/install.sh | HOME=/home/\(input.username) sh"],
+ ["sh", "-c", "systemctl enable --now code-server@\(input.username)"],
+ ["sh", "-c", "sleep 10"],
+ // TODO(gio): listen only on tailscale interface
+ ["sh", "-c", "sed -i -e 's/127.0.0.1/0.0.0.0/g' /home/\(input.username)/.config/code-server/config.yaml"],
+ ["sh", "-c", "sed -i -e 's/auth: password/auth: none/g' /home/\(input.username)/.config/code-server/config.yaml"],
+ ["sh", "-c", "systemctl restart --now code-server@\(input.username)"],
+ ]
+}
diff --git a/core/installer/vpn.go b/core/installer/vpn.go
new file mode 100644
index 0000000..4739bf2
--- /dev/null
+++ b/core/installer/vpn.go
@@ -0,0 +1,34 @@
+package installer
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "net/http"
+)
+
+type VPNAuthKeyGenerator interface {
+ Generate(username string) (string, error)
+}
+
+type headscaleAPIClient struct {
+ apiAddr string
+}
+
+func NewHeadscaleAPIClient(apiAddr string) VPNAuthKeyGenerator {
+ return &headscaleAPIClient{apiAddr}
+}
+
+func (g *headscaleAPIClient) Generate(username string) (string, error) {
+ resp, err := http.Post(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username), "application/json", nil)
+ if err != nil {
+ return "", err
+ }
+ var buf bytes.Buffer
+ io.Copy(&buf, resp.Body)
+ if resp.StatusCode != http.StatusOK {
+ return "", errors.New(buf.String())
+ }
+ return buf.String(), nil
+}
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 3f40e9f..bd72aef 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -101,6 +101,7 @@
env installer.EnvConfig
nsc installer.NamespaceCreator
jc installer.JobCreator
+ vpnKeyGen installer.VPNAuthKeyGenerator
workers map[string]map[string]struct{}
appConfigs map[string]appConfig
tmplts dodoAppTmplts
@@ -132,6 +133,7 @@
envAppManagerAddr string,
nsc installer.NamespaceCreator,
jc installer.JobCreator,
+ vpnKeyGen installer.VPNAuthKeyGenerator,
env installer.EnvConfig,
external bool,
fetchUsersAddr string,
@@ -166,6 +168,7 @@
env,
nsc,
jc,
+ vpnKeyGen,
map[string]map[string]struct{}{},
map[string]appConfig{},
tmplts,
@@ -919,7 +922,7 @@
return err
}
hf := installer.NewGitHelmFetcher()
- m, err := installer.NewAppManager(configRepo, s.nsc, s.jc, hf, "/")
+ m, err := installer.NewAppManager(configRepo, s.nsc, s.jc, hf, s.vpnKeyGen, "/")
if err != nil {
return err
}
@@ -1044,7 +1047,7 @@
return installer.ReleaseResources{}, err
}
hf := installer.NewGitHelmFetcher()
- m, err := installer.NewAppManager(repo, s.nsc, s.jc, hf, "/.dodo")
+ m, err := installer.NewAppManager(repo, s.nsc, s.jc, hf, s.vpnKeyGen, "/.dodo")
if err != nil {
return installer.ReleaseResources{}, err
}
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 9e45de5..08a8a1f 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -214,7 +214,7 @@
}
// TODO(gio): remove this once auto user sync is implemented
{
- appManager, err := installer.NewAppManager(s.repo, s.nsCreator, nil, s.hf, "/apps")
+ appManager, err := installer.NewAppManager(s.repo, s.nsCreator, nil, s.hf, nil, "/apps")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return