ClusterManager: Implements support of remote clusters.
After this change users will be able to:
* Create cluster and add/remove servers to it
* Install apps on remote cluster
* Move already installed apps between clusters
* Apps running on server being removed will auto-migrate
to another server from that same cluster
This is achieved by:
* Installing and running minimal version of dodo on remote cluster
* Ingress-nginx is installed automatically on new clusters
* Next to nginx we run VPN client in the same pod, so that
default cluster can establish secure communication with it
* Multiple reverse proxies are configured to get to the
remote cluster service from ingress installed on default cluster.
Next steps:
* Support remote clusters in dodo apps (prototype ready)
* Clean up old cluster when moving app to the new one. Currently
old cluster keeps running app pods even though no ingress can
reach it anymore.
Change-Id: Iffc908c93416d4126a8e1c2832eae7b659cb8044
diff --git a/core/installer/schema.go b/core/installer/schema.go
index fcdebd4..5a70519 100644
--- a/core/installer/schema.go
+++ b/core/installer/schema.go
@@ -23,6 +23,7 @@
KindArrayString = 8
KindPort = 9
KindVPNAuthKey = 11
+ KindCluster = 12
)
type Field struct {
@@ -56,6 +57,34 @@
advanced: true,
}
+const clusterSchema = `
+#Cluster: {
+ name: string
+ kubeconfig: string
+ ingressClassName: string
+}
+
+value: { %s }
+`
+
+func isCluster(v cue.Value) bool {
+ if v.Value().Kind() != cue.StructKind {
+ return false
+ }
+ s := fmt.Sprintf(clusterSchema, fmt.Sprintf("%#v", v))
+ c := cuecontext.New()
+ u := c.CompileString(s)
+ if err := u.Validate(); err != nil {
+ return false
+ }
+ cluster := u.LookupPath(cue.ParsePath("#Cluster"))
+ vv := u.LookupPath(cue.ParsePath("value"))
+ if err := cluster.Subsume(vv); err == nil {
+ return true
+ }
+ return false
+}
+
const networkSchema = `
#Network: {
name: string
@@ -233,18 +262,18 @@
meta := map[string]string{}
usernameFieldAttr := v.Attribute("usernameField")
if usernameFieldAttr.Err() == nil {
- meta["usernameField"] = strings.ToLower(usernameFieldAttr.Contents())
+ meta["usernameField"] = usernameFieldAttr.Contents()
}
usernameAttr := v.Attribute("username")
if usernameAttr.Err() == nil {
- meta["username"] = strings.ToLower(usernameAttr.Contents())
+ meta["username"] = usernameAttr.Contents()
}
if len(meta) != 1 {
return nil, fmt.Errorf("invalid vpn auth key field meta: %+v", meta)
}
enabledFieldAttr := v.Attribute("enabledField")
if enabledFieldAttr.Err() == nil {
- meta["enabledField"] = strings.ToLower(enabledFieldAttr.Contents())
+ meta["enabledField"] = enabledFieldAttr.Contents()
}
return basicSchema{name, KindVPNAuthKey, true, meta}, nil
} else {
@@ -272,9 +301,11 @@
return basicSchema{name, KindAuth, false, nil}, nil
} else if isSSHKey(v) {
return basicSchema{name, KindSSHKey, true, nil}, nil
+ } else if isCluster(v) {
+ return basicSchema{name, KindCluster, false, nil}, nil
}
s := structSchema{name, make([]Field, 0), false}
- f, err := v.Fields(cue.Schema())
+ f, err := v.Fields(cue.All())
if err != nil {
return nil, err
}
@@ -283,10 +314,14 @@
if err != nil {
return nil, err
}
- s.fields = append(s.fields, Field{f.Selector().String(), scm})
+ s.fields = append(s.fields, Field{cleanFieldName(f.Selector().String()), scm})
}
return s, nil
default:
return nil, fmt.Errorf("SHOULD NOT REACH!")
}
}
+
+func cleanFieldName(name string) string {
+ return strings.ReplaceAll(strings.ReplaceAll(name, "?", ""), "!", "")
+}