Installer: Handle custom networks/domains
Change-Id: Id88e82a0757365466d92fb31223e21b7199ef940
diff --git a/charts/dodo-app/templates/install.yaml b/charts/dodo-app/templates/install.yaml
index 50ff7ab..d63326d 100644
--- a/charts/dodo-app/templates/install.yaml
+++ b/charts/dodo-app/templates/install.yaml
@@ -122,6 +122,7 @@
- --api-port={{ .Values.apiPort }}
- --self={{ .Values.self }}
- --namespace={{ .Values.namespace }} # TODO(gio): maybe use .Release.Namespace ?
+ - --env-app-manager-addr={{ .Values.envAppManagerAddr }}
- --env-config=/pcloud/env-config/config.json
- --app-admin-key={{ .Values.appAdminKey }}
- --git-repo-public-key={{ .Values.gitRepoPublicKey }}
diff --git a/charts/dodo-app/values.yaml b/charts/dodo-app/values.yaml
index 50aae4d..4c0b788 100644
--- a/charts/dodo-app/values.yaml
+++ b/charts/dodo-app/values.yaml
@@ -9,6 +9,7 @@
sshPrivateKey: key
self: ""
namespace: ""
+envAppManagerAddr: ""
envConfig: ""
appAdminKey: ""
gitRepoPublicKey: ""
diff --git a/core/installer/app.go b/core/installer/app.go
index 1922b48..dcd2ca2 100644
--- a/core/installer/app.go
+++ b/core/installer/app.go
@@ -186,7 +186,7 @@
type EnvApp interface {
App
- Render(release Release, env EnvConfig, 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) (EnvAppRendered, error)
}
type cueApp struct {
@@ -435,8 +435,13 @@
return AppTypeEnv
}
-func (a cueEnvApp) Render(release Release, env EnvConfig, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (EnvAppRendered, error) {
- networks := CreateNetworks(env)
+func (a cueEnvApp) Render(
+ release Release,
+ env EnvConfig,
+ networks []Network,
+ values map[string]any,
+ charts map[string]helmv2.HelmChartTemplateSpec,
+) (EnvAppRendered, error) {
derived, err := deriveValues(values, a.Schema(), networks)
if err != nil {
return EnvAppRendered{}, err
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 0930356..4fc0b1f 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -87,7 +87,11 @@
func (m *AppManager) FindAllAppInstances(name string) ([]AppInstanceConfig, error) {
kust, err := soft.ReadKustomization(m.repoIO, filepath.Join(m.appDirRoot, "kustomization.yaml"))
if err != nil {
- return nil, err
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, nil
+ } else {
+ return nil, err
+ }
}
ret := make([]AppInstanceConfig, 0)
for _, app := range kust.Resources {
@@ -404,6 +408,16 @@
return ReleaseResources{}, err
}
}
+ var networks []Network
+ if o.Networks != nil {
+ networks = o.Networks
+ } else {
+ var err error
+ networks, err = m.CreateNetworks(env)
+ if err != nil {
+ return ReleaseResources{}, err
+ }
+ }
var lg LocalChartGenerator
if o.LG != nil {
lg = o.LG
@@ -416,7 +430,7 @@
RepoAddr: m.repoIO.FullAddress(),
AppDir: appDir,
}
- rendered, err := app.Render(release, env, values, nil)
+ rendered, err := app.Render(release, env, networks, values, nil)
if err != nil {
return ReleaseResources{}, err
}
@@ -447,7 +461,7 @@
if o.FetchContainerImages {
release.ImageRegistry = imageRegistry
}
- rendered, err = app.Render(release, env, values, localCharts)
+ rendered, err = app.Render(release, env, networks, values, localCharts)
if err != nil {
return ReleaseResources{}, err
}
@@ -456,7 +470,6 @@
}
// TODO(gio): add ingress-nginx to release resources
if err := openPorts(rendered.Ports, portReservations, allocators); err != nil {
- fmt.Println(err)
return ReleaseResources{}, err
}
return ReleaseResources{
@@ -531,7 +544,11 @@
if err != nil {
return ReleaseResources{}, err
}
- rendered, err := app.Render(config.Release, env, values, renderedCfg.LocalCharts)
+ networks, err := m.CreateNetworks(env)
+ if err != nil {
+ return ReleaseResources{}, err
+ }
+ rendered, err := app.Render(config.Release, env, networks, values, renderedCfg.LocalCharts)
if err != nil {
return ReleaseResources{}, err
}
@@ -563,15 +580,14 @@
return err
}
if err := closePorts(portForward); err != nil {
- fmt.Println(err)
return err
}
return nil
}
// TODO(gio): deduplicate with cue definition in app.go, this one should be removed.
-func CreateNetworks(env EnvConfig) []Network {
- return []Network{
+func (m *AppManager) CreateNetworks(env EnvConfig) ([]Network, error) {
+ ret := []Network{
{
Name: "Public",
IngressClass: fmt.Sprintf("%s-ingress-public", env.InfraName),
@@ -590,11 +606,28 @@
DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/remove", env.Id),
},
}
+ n, err := m.FindAllAppInstances("network")
+ if err != nil {
+ return nil, err
+ }
+ for _, a := range n {
+ ret = append(ret, Network{
+ Name: a.Input["name"].(string),
+ IngressClass: fmt.Sprintf("%s-ingress-public", env.InfraName),
+ CertificateIssuer: fmt.Sprintf("%s-public", env.Id),
+ Domain: a.Input["domain"].(string),
+ AllocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/allocate", env.InfraName),
+ ReservePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/reserve", env.InfraName),
+ DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/remove", env.InfraName),
+ })
+ }
+ return ret, nil
}
type installOptions struct {
NoPublish bool
Env *EnvConfig
+ Networks []Network
Branch string
LG LocalChartGenerator
FetchContainerImages bool
@@ -610,6 +643,12 @@
}
}
+func WithNetworks(networks []Network) InstallOption {
+ return func(o *installOptions) {
+ o.Networks = networks
+ }
+}
+
func WithBranch(branch string) InstallOption {
return func(o *installOptions) {
o.Branch = branch
diff --git a/core/installer/app_repository.go b/core/installer/app_repository.go
index 27b9b43..df35ae8 100644
--- a/core/installer/app_repository.go
+++ b/core/installer/app_repository.go
@@ -33,6 +33,7 @@
// "values-tmpl/qbittorrent.cue",
// "values-tmpl/jellyfin.cue",
"values-tmpl/rpuppy.cue",
+ "values-tmpl/certificate-issuer-custom.cue",
}
var envAppConfigs = []string{
diff --git a/core/installer/app_test.go b/core/installer/app_test.go
index 7935ec2..7e64d50 100644
--- a/core/installer/app_test.go
+++ b/core/installer/app_test.go
@@ -2,28 +2,51 @@
import (
_ "embed"
+ "fmt"
"net"
"testing"
)
-var env = EnvConfig{
- InfraName: "dodo",
- Id: "id",
- ContactEmail: "foo@bar.ge",
- Domain: "bar.ge",
- PrivateDomain: "p.bar.ge",
- PublicIP: []net.IP{net.ParseIP("1.2.3.4")},
- NameserverIP: []net.IP{net.ParseIP("1.2.3.4")},
- NamespacePrefix: "id-",
- Network: EnvNetwork{
- DNS: net.ParseIP("1.1.1.1"),
- DNSInClusterIP: net.ParseIP("2.2.2.2"),
- Ingress: net.ParseIP("3.3.3.3"),
- Headscale: net.ParseIP("4.4.4.4"),
- ServicesFrom: net.ParseIP("5.5.5.5"),
- ServicesTo: net.ParseIP("6.6.6.6"),
- },
-}
+var (
+ env = EnvConfig{
+ InfraName: "dodo",
+ Id: "id",
+ ContactEmail: "foo@bar.ge",
+ Domain: "bar.ge",
+ PrivateDomain: "p.bar.ge",
+ PublicIP: []net.IP{net.ParseIP("1.2.3.4")},
+ NameserverIP: []net.IP{net.ParseIP("1.2.3.4")},
+ NamespacePrefix: "id-",
+ Network: EnvNetwork{
+ DNS: net.ParseIP("1.1.1.1"),
+ DNSInClusterIP: net.ParseIP("2.2.2.2"),
+ Ingress: net.ParseIP("3.3.3.3"),
+ Headscale: net.ParseIP("4.4.4.4"),
+ ServicesFrom: net.ParseIP("5.5.5.5"),
+ ServicesTo: net.ParseIP("6.6.6.6"),
+ },
+ }
+
+ networks = []Network{
+ {
+ Name: "Public",
+ IngressClass: fmt.Sprintf("%s-ingress-public", env.InfraName),
+ CertificateIssuer: fmt.Sprintf("%s-public", env.Id),
+ Domain: env.Domain,
+ AllocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/allocate", env.InfraName),
+ ReservePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/reserve", env.InfraName),
+ DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-public.svc.cluster.local/api/remove", env.InfraName),
+ },
+ {
+ Name: "Private",
+ IngressClass: fmt.Sprintf("%s-ingress-private", env.Id),
+ Domain: env.PrivateDomain,
+ AllocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/allocate", env.Id),
+ ReservePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/reserve", env.Id),
+ DeallocatePortAddr: fmt.Sprintf("http://port-allocator.%s-ingress-private.svc.cluster.local/api/remove", env.Id),
+ },
+ }
+)
func TestAuthProxyEnabled(t *testing.T) {
r := NewInMemoryAppRepository(CreateAllApps())
@@ -46,7 +69,7 @@
"groups": "a,b",
},
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -76,7 +99,7 @@
"enabled": false,
},
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -101,7 +124,7 @@
values := map[string]any{
"authGroups": "foo,bar",
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -131,7 +154,7 @@
},
"sshPort": 22,
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -156,7 +179,7 @@
"subdomain": "jenkins",
"network": "Private",
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -215,7 +238,7 @@
},
"sshPrivateKey": "private",
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -248,7 +271,7 @@
"groups": "a,b",
},
}
- rendered, err := app.Render(release, env, values, nil)
+ rendered, err := app.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
@@ -314,7 +337,7 @@
RepoAddr: "ssh://192.168.100.210:22/config",
AppDir: "/foo/bar",
}
- _, err = app.Render(release, env, map[string]any{
+ _, err = app.Render(release, env, networks, map[string]any{
"repoAddr": "",
"managerAddr": "",
"appId": "",
@@ -342,7 +365,7 @@
"repoHost": "",
"gitRepoPublicKey": "",
}
- rendered, err := a.Render(release, env, values, nil)
+ rendered, err := a.Render(release, env, networks, values, nil)
if err != nil {
t.Fatal(err)
}
diff --git a/core/installer/cmd/dodo_app.go b/core/installer/cmd/dodo_app.go
index a6521d8..2eb5b8f 100644
--- a/core/installer/cmd/dodo_app.go
+++ b/core/installer/cmd/dodo_app.go
@@ -17,16 +17,17 @@
)
var dodoAppFlags struct {
- port int
- apiPort int
- sshKey string
- repoAddr string
- self string
- namespace string
- envConfig string
- appAdminKey string
- gitRepoPublicKey string
- db string
+ port int
+ apiPort int
+ sshKey string
+ repoAddr string
+ self string
+ namespace string
+ envAppManagerAddr string
+ envConfig string
+ appAdminKey string
+ gitRepoPublicKey string
+ db string
}
func dodoAppCmd() *cobra.Command {
@@ -77,6 +78,12 @@
"",
)
cmd.Flags().StringVar(
+ &dodoAppFlags.envAppManagerAddr,
+ "env-app-manager-addr",
+ "",
+ "",
+ )
+ cmd.Flags().StringVar(
&dodoAppFlags.envConfig,
"env-config",
"",
@@ -152,6 +159,7 @@
dodoAppFlags.gitRepoPublicKey,
softClient,
dodoAppFlags.namespace,
+ dodoAppFlags.envAppManagerAddr,
nsc,
jc,
env,
diff --git a/core/installer/values-tmpl/certificate-issuer-custom.cue b/core/installer/values-tmpl/certificate-issuer-custom.cue
new file mode 100644
index 0000000..382e8fa
--- /dev/null
+++ b/core/installer/values-tmpl/certificate-issuer-custom.cue
@@ -0,0 +1,41 @@
+input: {
+ name: string
+ domain: string
+}
+
+images: {}
+
+name: "Network"
+namespace: "ingress-custom"
+readme: "Configure custom public domain"
+description: readme
+icon: "<svg xmlns='http://www.w3.org/2000/svg' width='50' height='50' viewBox='0 0 48 48'><g fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='4'><path d='M4 34h8v8H4zM8 6h32v12H8zm16 28V18'/><path d='M8 34v-8h32v8m-4 0h8v8h-8zm-16 0h8v8h-8zm-6-22h2'/></g></svg>"
+
+charts: {
+ "certificate-issuer-public": {
+ kind: "GitRepository"
+ address: "https://github.com/giolekva/pcloud.git"
+ branch: "main"
+ path: "charts/certificate-issuer-public"
+ }
+}
+
+helm: {
+ "certificate-issuer-public": {
+ chart: charts["certificate-issuer-public"]
+ dependsOn: [{
+ name: "ingress-nginx"
+ namespace: "\(global.namespacePrefix)ingress-private"
+ }]
+ values: {
+ issuer: {
+ name: input.name
+ server: "https://acme-v02.api.letsencrypt.org/directory"
+ // server: "https://acme-staging-v02.api.letsencrypt.org/directory"
+ domain: input.domain
+ contactEmail: global.contactEmail
+ ingressClass: ingressPublic
+ }
+ }
+ }
+}
diff --git a/core/installer/values-tmpl/dodo-app.cue b/core/installer/values-tmpl/dodo-app.cue
index ddd3b17..a6d342d 100644
--- a/core/installer/values-tmpl/dodo-app.cue
+++ b/core/installer/values-tmpl/dodo-app.cue
@@ -112,6 +112,7 @@
sshPrivateKey: base64.Encode(null, input.dAppKeys.private)
self: "api.\(release.namespace).svc.cluster.local"
namespace: release.namespace
+ envAppManagerAddr: "http://appmanager.\(global.namespacePrefix)appmanager.svc.cluster.local"
envConfig: base64.Encode(null, json.Marshal(global))
appAdminKey: input.adminKey
gitRepoPublicKey: input.ssKeys.public
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index a4430b8..2c434c6 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -93,6 +93,7 @@
func (s *AppManagerServer) Start() error {
r := mux.NewRouter()
r.PathPrefix("/static/").Handler(cachingHandler{http.FileServer(http.FS(staticAssets))})
+ r.HandleFunc("/api/networks", s.handleNetworks).Methods(http.MethodGet)
r.HandleFunc("/api/app-repo", s.handleAppRepo)
r.HandleFunc("/api/app/{slug}/install", s.handleAppInstall).Methods(http.MethodPost)
r.HandleFunc("/api/app/{slug}", s.handleApp).Methods(http.MethodGet)
@@ -116,6 +117,23 @@
Instances []installer.AppInstanceConfig `json:"instances,omitempty"`
}
+func (s *AppManagerServer) handleNetworks(w http.ResponseWriter, r *http.Request) {
+ env, err := s.m.Config()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ networks, err := s.m.CreateNetworks(env)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if err := json.NewEncoder(w).Encode(networks); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
func (s *AppManagerServer) handleAppRepo(w http.ResponseWriter, r *http.Request) {
all, err := s.r.GetAll()
if err != nil {
@@ -414,10 +432,15 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+ networks, err := s.m.CreateNetworks(global)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
data := appPageData{
App: a,
Instances: instances,
- AvailableNetworks: installer.CreateNetworks(global),
+ AvailableNetworks: networks,
CurrentPage: a.Name(),
}
if err := s.tmpl.app.Execute(w, data); err != nil {
@@ -452,12 +475,17 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+ networks, err := s.m.CreateNetworks(global)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
t := s.tasks[slug]
data := appPageData{
App: a,
Instance: instance,
Instances: instances,
- AvailableNetworks: installer.CreateNetworks(global),
+ AvailableNetworks: networks,
Task: t,
CurrentPage: instance.Id,
}
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 75d12b9..20e881d 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -29,21 +29,22 @@
)
type DodoAppServer struct {
- l sync.Locker
- st Store
- port int
- apiPort int
- self string
- sshKey string
- gitRepoPublicKey string
- client soft.Client
- namespace string
- env installer.EnvConfig
- nsc installer.NamespaceCreator
- jc installer.JobCreator
- workers map[string]map[string]struct{}
- appNs map[string]string
- sc *securecookie.SecureCookie
+ l sync.Locker
+ st Store
+ port int
+ apiPort int
+ self string
+ sshKey string
+ gitRepoPublicKey string
+ client soft.Client
+ namespace string
+ envAppManagerAddr string
+ env installer.EnvConfig
+ nsc installer.NamespaceCreator
+ jc installer.JobCreator
+ workers map[string]map[string]struct{}
+ appNs map[string]string
+ sc *securecookie.SecureCookie
}
// TODO(gio): Initialize appNs on startup
@@ -56,6 +57,7 @@
gitRepoPublicKey string,
client soft.Client,
namespace string,
+ envAppManagerAddr string,
nsc installer.NamespaceCreator,
jc installer.JobCreator,
env installer.EnvConfig,
@@ -74,6 +76,7 @@
gitRepoPublicKey,
client,
namespace,
+ envAppManagerAddr,
env,
nsc,
jc,
@@ -277,7 +280,11 @@
}
// TODO(gio): Create commit record on app init as well
go func() {
- if err := s.updateDodoApp(req.Repository.Name, s.appNs[req.Repository.Name]); err != nil {
+ networks, err := getNetworks(fmt.Sprintf("%s/api/networks", s.envAppManagerAddr))
+ if err != nil {
+ return
+ }
+ if err := s.updateDodoApp(req.Repository.Name, s.appNs[req.Repository.Name], networks); err != nil {
if err := s.st.CreateCommit(req.Repository.Name, req.After, err.Error()); err != nil {
fmt.Printf("Error: %s\n", err.Error())
return
@@ -417,7 +424,11 @@
}
namespace := fmt.Sprintf("%s%s%s", s.env.NamespacePrefix, app.Namespace(), suffix)
s.appNs[appName] = namespace
- if err := s.updateDodoApp(appName, namespace); err != nil {
+ networks, err := getNetworks(fmt.Sprintf("%s/api/networks", s.envAppManagerAddr))
+ if err != nil {
+ return "", err
+ }
+ if err := s.updateDodoApp(appName, namespace, networks); err != nil {
return "", err
}
repo, err := s.client.GetRepo(ConfigRepoName)
@@ -449,6 +460,7 @@
"gitRepoPublicKey": s.gitRepoPublicKey,
},
installer.WithConfig(&s.env),
+ installer.WithNetworks(networks),
installer.WithNoPublish(),
installer.WithNoLock(),
); err != nil {
@@ -509,7 +521,7 @@
}
}
-func (s *DodoAppServer) updateDodoApp(name, namespace string) error {
+func (s *DodoAppServer) updateDodoApp(name, namespace string, networks []installer.Network) error {
repo, err := s.client.GetRepo(name)
if err != nil {
return err
@@ -540,6 +552,7 @@
"sshPrivateKey": s.sshKey,
},
installer.WithConfig(&s.env),
+ installer.WithNetworks(networks),
installer.WithLocalChartGenerator(lg),
installer.WithBranch("dodo"),
installer.WithForce(),
@@ -620,3 +633,15 @@
func generatePassword() string {
return "foo"
}
+
+func getNetworks(addr string) ([]installer.Network, error) {
+ resp, err := http.Get(addr)
+ if err != nil {
+ return nil, err
+ }
+ ret := []installer.Network{}
+ if json.NewDecoder(resp.Body).Decode(&ret); err != nil {
+ return nil, err
+ }
+ return ret, nil
+}