Dodo APP: infrastructure to deploy app by pusing to Git repo

Change-Id: I4034c6893255581b014ddb207c844261cb34202b
diff --git a/core/installer/app.go b/core/installer/app.go
index 1626a1a..2a16c1e 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -2,6 +2,7 @@
 
 import (
 	"bytes"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	template "html/template"
@@ -16,8 +17,15 @@
 	cueyaml "cuelang.org/go/encoding/yaml"
 )
 
+//go:embed pcloud_app.cue
+var DodoAppCue []byte
+
 // TODO(gio): import
 const cueEnvAppGlobal = `
+import (
+    "net"
+)
+
 #Global: {
 	id: string | *""
 	pcloudEnvName: string | *""
@@ -31,20 +39,13 @@
 	network: #EnvNetwork
 }
 
-networks: {
-	public: #Network & {
-		name: "Public"
-		ingressClass: "\(global.pcloudEnvName)-ingress-public"
-		certificateIssuer: "\(global.id)-public"
-		domain: global.domain
-		allocatePortAddr: "http://port-allocator.\(global.pcloudEnvName)-ingress-public.svc.cluster.local/api/allocate"
-	}
-	private: #Network & {
-		name: "Private"
-		ingressClass: "\(global.id)-ingress-private"
-		domain: global.privateDomain
-		allocatePortAddr: "http://port-allocator.\(global.id)-ingress-private.svc.cluster.local/api/allocate"
-	}
+#EnvNetwork: {
+	dns: net.IPv4
+	dnsInClusterIP: net.IPv4
+	ingress: net.IPv4
+	headscale: net.IPv4
+	servicesFrom: net.IPv4
+	servicesTo: net.IPv4
 }
 
 // TODO(gio): remove
@@ -164,10 +165,6 @@
 `
 
 const cueBaseConfig = `
-import (
-  "net"
-)
-
 name: string | *""
 description: string | *""
 readme: string | *""
@@ -187,9 +184,11 @@
 #AppType: "infra" | "env"
 appType: #AppType | *"env"
 
-#Auth: {
-  enabled: bool | *false // TODO(gio): enabled by default?
-  groups: string | *"" // TODO(gio): []string
+#Release: {
+	appInstanceId: string
+	namespace: string
+	repoAddr: string
+	appDir: string
 }
 
 #Network: {
@@ -200,6 +199,11 @@
 	allocatePortAddr: string
 }
 
+#Auth: {
+  enabled: bool | *false // TODO(gio): enabled by default?
+  groups: string | *"" // TODO(gio): []string
+}
+
 #Image: {
 	registry: string | *"docker.io"
 	repository: string
@@ -222,22 +226,6 @@
 	namespace: string // TODO(gio): default global.id
 }
 
-#EnvNetwork: {
-	dns: net.IPv4
-	dnsInClusterIP: net.IPv4
-	ingress: net.IPv4
-	headscale: net.IPv4
-	servicesFrom: net.IPv4
-	servicesTo: net.IPv4
-}
-
-#Release: {
-	appInstanceId: string
-	namespace: string
-	repoAddr: string
-	appDir: string
-}
-
 #PortForward: {
 	allocator: string
 	protocol: "TCP" | "UDP" | *"TCP"
@@ -302,6 +290,8 @@
 	}
 }
 
+resources: {}
+
 #HelmRelease: {
 	_name: string
 	_chart: #Chart
@@ -349,6 +339,8 @@
 help: [...#HelpDocument] | *[]
 
 url: string | *""
+
+networks: {}
 `
 
 type rendered struct {
@@ -620,17 +612,34 @@
 	if err := res.LookupPath(cue.ParsePath("portForward")).Decode(&ret.Ports); err != nil {
 		return rendered{}, err
 	}
-	output := res.LookupPath(cue.ParsePath("output"))
-	i, err := output.Fields()
-	if err != nil {
-		return rendered{}, err
-	}
-	for i.Next() {
-		if contents, err := cueyaml.Encode(i.Value()); err != nil {
+	{
+		output := res.LookupPath(cue.ParsePath("output"))
+		i, err := output.Fields()
+		if err != nil {
 			return rendered{}, err
-		} else {
-			name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
-			ret.Resources[name] = contents
+		}
+		for i.Next() {
+			if contents, err := cueyaml.Encode(i.Value()); err != nil {
+				return rendered{}, err
+			} else {
+				name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
+				ret.Resources[name] = contents
+			}
+		}
+	}
+	{
+		resources := res.LookupPath(cue.ParsePath("resources"))
+		i, err := resources.Fields()
+		if err != nil {
+			return rendered{}, err
+		}
+		for i.Next() {
+			if contents, err := cueyaml.Encode(i.Value()); err != nil {
+				return rendered{}, err
+			} else {
+				name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
+				ret.Resources[name] = contents
+			}
 		}
 	}
 	helpValue := res.LookupPath(cue.ParsePath("help"))
@@ -664,6 +673,15 @@
 	return cueEnvApp{app}, nil
 }
 
+func NewDodoApp(appCfg []byte) (EnvApp, error) {
+	return NewCueEnvApp(CueAppData{
+		"app.cue":        appCfg,
+		"base.cue":       []byte(cueBaseConfig),
+		"pcloud_app.cue": DodoAppCue,
+		"env_app.cue":    []byte(cueEnvAppGlobal),
+	})
+}
+
 func (a cueEnvApp) Type() AppType {
 	return AppTypeEnv
 }
@@ -675,9 +693,10 @@
 		return EnvAppRendered{}, nil
 	}
 	ret, err := a.cueApp.render(map[string]any{
-		"global":  env,
-		"release": release,
-		"input":   derived,
+		"global":   env,
+		"release":  release,
+		"input":    derived,
+		"networks": networkMap(networks),
 	})
 	if err != nil {
 		return EnvAppRendered{}, err
@@ -747,3 +766,11 @@
 	}
 	return strings.Join(tmp, ",")
 }
+
+func networkMap(networks []Network) map[string]Network {
+	ret := make(map[string]Network)
+	for _, n := range networks {
+		ret[strings.ToLower(n.Name)] = n
+	}
+	return ret
+}