Installer: Clean up RepoIO interface
Change-Id: If80d7be1460c725b7df9d1d27c9354cb9141acfe
diff --git a/core/installer/app_manager.go b/core/installer/app_manager.go
index 295c8ff..e438a26 100644
--- a/core/installer/app_manager.go
+++ b/core/installer/app_manager.go
@@ -1,8 +1,14 @@
package installer
import (
+ "bytes"
+ "encoding/json"
+ "errors"
"fmt"
+ "io/fs"
"io/ioutil"
+ "net/http"
+ "path"
"path/filepath"
"sigs.k8s.io/yaml"
@@ -25,15 +31,58 @@
}
func (m *AppManager) Config() (Config, error) {
- return m.repoIO.ReadConfig()
+ var cfg Config
+ if err := ReadYaml(m.repoIO, configFileName, &cfg); err != nil {
+ return Config{}, err
+ } else {
+ return cfg, nil
+ }
+}
+
+func (m *AppManager) appConfig(path string) (AppConfig, error) {
+ var cfg AppConfig
+ if err := ReadYaml(m.repoIO, path, &cfg); err != nil {
+ return AppConfig{}, err
+ } else {
+ return cfg, nil
+ }
}
func (m *AppManager) FindAllInstances(name string) ([]AppConfig, error) {
- return m.repoIO.FindAllInstances(appDir, name)
+ kust, err := ReadKustomization(m.repoIO, filepath.Join(appDir, "kustomization.yaml"))
+ if err != nil {
+ return nil, err
+ }
+ ret := make([]AppConfig, 0)
+ for _, app := range kust.Resources {
+ cfg, err := m.appConfig(filepath.Join(appDir, app, "config.yaml"))
+ if err != nil {
+ return nil, err
+ }
+ cfg.Id = app
+ if cfg.AppId == name {
+ ret = append(ret, cfg)
+ }
+ }
+ return ret, nil
}
func (m *AppManager) FindInstance(id string) (AppConfig, error) {
- return m.repoIO.FindInstance(appDir, id)
+ kust, err := ReadKustomization(m.repoIO, filepath.Join(appDir, "kustomization.yaml"))
+ if err != nil {
+ return AppConfig{}, err
+ }
+ for _, app := range kust.Resources {
+ if app == id {
+ cfg, err := m.appConfig(filepath.Join(appDir, app, "config.yaml"))
+ if err != nil {
+ return AppConfig{}, err
+ }
+ cfg.Id = id
+ return cfg, nil
+ }
+ }
+ return AppConfig{}, nil
}
func (m *AppManager) AppConfig(name string) (AppConfig, error) {
@@ -51,58 +100,146 @@
return cfg, err
}
-func (m *AppManager) Install(app App, ns NamespaceGenerator, suffixGen SuffixGenerator, config map[string]any) error {
+type allocatePortReq struct {
+ Protocol string `json:"protocol"`
+ SourcePort int `json:"sourcePort"`
+ TargetService string `json:"targetService"`
+ TargetPort int `json:"targetPort"`
+}
+
+func openPorts(ports []PortForward) error {
+ for _, p := range ports {
+ var buf bytes.Buffer
+ req := allocatePortReq{
+ Protocol: p.Protocol,
+ SourcePort: p.SourcePort,
+ TargetService: p.TargetService,
+ TargetPort: p.TargetPort,
+ }
+ if err := json.NewEncoder(&buf).Encode(req); err != nil {
+ return err
+ }
+ resp, err := http.Post(p.Allocator, "application/json", &buf)
+ if err != nil {
+ return err
+ }
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("Could not allocate port %d, status code: %d", p.SourcePort, resp.StatusCode)
+ }
+ }
+ return nil
+}
+
+func createKustomizationChain(r RepoFS, path string) error {
+ for p := filepath.Clean(path); p != "/"; {
+ parent, child := filepath.Split(p)
+ kustPath := filepath.Join(parent, "kustomization.yaml")
+ kust, err := ReadKustomization(r, kustPath)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ k := NewKustomization()
+ kust = &k
+ } else {
+ return err
+ }
+ }
+ kust.AddResources(child)
+ if err := WriteYaml(r, kustPath, kust); err != nil {
+ return err
+ }
+ p = filepath.Clean(parent)
+ }
+ return nil
+}
+
+func InstallApp(repo RepoIO, nsc NamespaceCreator, app App, appDir string, namespace string, initValues map[string]any, derived Derived) error {
+ if err := nsc.Create(namespace); err != nil {
+ return err
+ }
+ derived.Release = Release{
+ Namespace: namespace,
+ RepoAddr: repo.FullAddress(),
+ AppDir: appDir,
+ }
+ rendered, err := app.Render(derived)
+ if err != nil {
+ return err
+ }
+ if err := openPorts(rendered.Ports); err != nil {
+ return err
+ }
+ return repo.Atomic(func(r RepoFS) (string, error) {
+ if err := createKustomizationChain(r, appDir); err != nil {
+ return "", err
+ }
+ {
+ if err := r.RemoveDir(appDir); err != nil {
+ return "", err
+ }
+ if err := r.CreateDir(appDir); err != nil {
+ return "", err
+ }
+ cfg := AppConfig{
+ AppId: app.Name(),
+ Config: initValues,
+ Derived: derived,
+ }
+ if err := WriteYaml(r, path.Join(appDir, configFileName), cfg); err != nil {
+ return "", err
+ }
+ }
+ {
+ appKust := NewKustomization()
+ for name, contents := range rendered.Resources {
+ appKust.AddResources(name)
+ out, err := r.Writer(path.Join(appDir, name))
+ if err != nil {
+ return "", err
+ }
+ defer out.Close()
+ if _, err := out.Write(contents); err != nil {
+ return "", err
+ }
+ }
+ if err := WriteYaml(r, path.Join(appDir, "kustomization.yaml"), appKust); err != nil {
+ return "", err
+ }
+ }
+ return fmt.Sprintf("install: %s", app.Name()), nil
+ })
+}
+
+func (m *AppManager) Install(app App, appDir string, namespace string, values map[string]any) error {
+ appDir = filepath.Clean(appDir)
if err := m.repoIO.Pull(); err != nil {
return err
}
- suffix, err := suffixGen.Generate()
+ globalConfig, err := m.Config()
if err != nil {
return err
}
- nms, err := ns.Generate(app.Namespace())
+ derivedValues, err := deriveValues(values, app.Schema(), CreateNetworks(globalConfig))
if err != nil {
return err
}
- nms = nms + suffix
- if err := m.nsCreator.Create(nms); err != nil {
- return err
- }
- globalConfig, err := m.repoIO.ReadConfig()
- if err != nil {
- return err
- }
- derivedValues, err := deriveValues(config, app.Schema(), CreateNetworks(globalConfig))
- if err != nil {
- fmt.Println(err)
- return err
- }
derived := Derived{
Global: globalConfig.Values,
Values: derivedValues,
}
- derived.Release.Namespace = nms
- fmt.Printf("%+v\n", derived)
- err = m.repoIO.InstallApp(
- app,
- filepath.Join(appDir, app.Name()+suffix),
- config,
- derived,
- )
- fmt.Println(err)
- return err
+ return InstallApp(m.repoIO, m.nsCreator, app, appDir, namespace, values, derived)
}
func (m *AppManager) Update(app App, instanceId string, config map[string]any) error {
if err := m.repoIO.Pull(); err != nil {
return err
}
- globalConfig, err := m.repoIO.ReadConfig()
+ globalConfig, err := m.Config()
if err != nil {
return err
}
instanceDir := filepath.Join(appDir, instanceId)
instanceConfigPath := filepath.Join(instanceDir, configFileName)
- appConfig, err := m.repoIO.ReadAppConfig(instanceConfigPath)
+ appConfig, err := m.appConfig(instanceConfigPath)
if err != nil {
return err
}
@@ -115,14 +252,24 @@
Release: appConfig.Derived.Release,
Values: derivedValues,
}
- return m.repoIO.InstallApp(app, instanceDir, config, derived)
+ return InstallApp(m.repoIO, m.nsCreator, app, instanceDir, appConfig.Derived.Release.Namespace, config, derived)
}
func (m *AppManager) Remove(instanceId string) error {
if err := m.repoIO.Pull(); err != nil {
return err
}
- return m.repoIO.RemoveApp(filepath.Join(appDir, instanceId))
+ return m.repoIO.Atomic(func(r RepoFS) (string, error) {
+ r.RemoveDir(filepath.Join(appDir, instanceId))
+ kustPath := filepath.Join(appDir, "kustomization.yaml")
+ kust, err := ReadKustomization(r, kustPath)
+ if err != nil {
+ return "", err
+ }
+ kust.RemoveResources(instanceId)
+ WriteYaml(r, kustPath, kust)
+ return fmt.Sprintf("uninstall: %s", instanceId), nil
+ })
}
// TODO(gio): deduplicate with cue definition in app.go, this one should be removed.
diff --git a/core/installer/bootstrapper.go b/core/installer/bootstrapper.go
index b981573..ad01140 100644
--- a/core/installer/bootstrapper.go
+++ b/core/installer/bootstrapper.go
@@ -24,13 +24,14 @@
const dnsAPIConfigMapName = "api-config"
type Bootstrapper struct {
- cl ChartLoader
- ns NamespaceCreator
- ha HelmActionConfigFactory
+ cl ChartLoader
+ ns NamespaceCreator
+ ha HelmActionConfigFactory
+ appRepo AppRepository
}
-func NewBootstrapper(cl ChartLoader, ns NamespaceCreator, ha HelmActionConfigFactory) Bootstrapper {
- return Bootstrapper{cl, ns, ha}
+func NewBootstrapper(cl ChartLoader, ns NamespaceCreator, ha HelmActionConfigFactory, appRepo AppRepository) Bootstrapper {
+ return Bootstrapper{cl, ns, ha, appRepo}
}
func (b Bootstrapper) Run(env EnvConfig) error {
@@ -75,30 +76,32 @@
fmt.Println("Failed to get config repo")
return err
}
- repoIO := NewRepoIO(repo, ss.Signer)
+ repoIO, err := NewRepoIO(repo, ss.Signer)
+ if err != nil {
+ return err
+ }
fmt.Println("Configuring main repo")
if err := configureMainRepo(repoIO, env); err != nil {
return err
}
fmt.Println("Installing infrastructure services")
- nsGen := NewPrefixGenerator(env.NamespacePrefix)
- if err := b.installInfrastructureServices(repoIO, nsGen, b.ns, env); err != nil {
+ if err := b.installInfrastructureServices(repoIO, env); err != nil {
return err
}
fmt.Println("Installing DNS Zone Manager")
- if err := b.installDNSZoneManager(ss, repoIO, nsGen, b.ns, env); err != nil {
+ if err := b.installDNSZoneManager(repoIO, env); err != nil {
return err
}
fmt.Println("Installing Fluxcd Reconciler")
- if err := b.installFluxcdReconciler(ss, repoIO, nsGen, b.ns, env); err != nil {
+ if err := b.installFluxcdReconciler(repoIO, ss, env); err != nil {
return err
}
fmt.Println("Installing env manager")
- if err := b.installEnvManager(ss, repoIO, nsGen, b.ns, env); err != nil {
+ if err := b.installEnvManager(repoIO, ss, env); err != nil {
return err
}
fmt.Println("Installing Ory Hydra Maester")
- if err := b.installOryHydraMaester(ss, repoIO, nsGen, b.ns, env); err != nil {
+ if err := b.installOryHydraMaester(repoIO, env); err != nil {
return err
}
fmt.Println("Environment ready to use")
@@ -320,8 +323,20 @@
if err != nil {
return err
}
- repoIO := NewRepoIO(repo, ss.Signer)
- if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s systems", envName), "readme"); err != nil {
+ repoIO, err := NewRepoIO(repo, ss.Signer)
+ if err != nil {
+ return err
+ }
+ if err := repoIO.Atomic(func(r RepoFS) (string, error) {
+ w, err := r.Writer("README.md")
+ if err != nil {
+ return "", err
+ }
+ if _, err := fmt.Fprintf(w, "# %s systems", envName); err != nil {
+ return "", err
+ }
+ return "readme", nil
+ }); err != nil {
return err
}
fmt.Println("Installing Flux")
@@ -380,31 +395,20 @@
return nil
}
-func (b Bootstrapper) installInfrastructureServices(repo RepoIO, nsGen NamespaceGenerator, nsCreator NamespaceCreator, env EnvConfig) error {
- appRepo := NewInMemoryAppRepository(CreateAllApps())
+func (b Bootstrapper) installInfrastructureServices(repo RepoIO, env EnvConfig) error {
install := func(name string) error {
fmt.Printf("Installing infrastructure service %s\n", name)
- app, err := appRepo.Find(name)
+ app, err := b.appRepo.Find(name)
if err != nil {
return err
}
- nms, err := nsGen.Generate(app.Namespace())
- if err != nil {
- return err
- }
- if err := nsCreator.Create(nms); err != nil {
- return err
- }
+ namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
derived := Derived{
Global: Values{
PCloudEnvName: env.Name,
},
- Release: Release{},
- Values: make(map[string]any),
}
- derived.Release.Namespace = nms
- values := map[string]any{}
- return repo.InstallApp(app, filepath.Join("/infrastructure", app.Name()), values, derived)
+ return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, nil, derived)
}
appsToInstall := []string{
"resource-renderer-controller",
@@ -422,28 +426,29 @@
}
func configureMainRepo(repo RepoIO, env EnvConfig) error {
- if err := repo.WriteYaml("config.yaml", env); err != nil {
- return err
- }
- if err := repo.WriteYaml("env-cidrs.yaml", EnvCIDRs{}); err != nil {
- return err
- }
- kust := NewKustomization()
- kust.AddResources(
- fmt.Sprintf("%s-flux", env.Name),
- "infrastructure",
- "environments",
- )
- if err := repo.WriteKustomization("kustomization.yaml", kust); err != nil {
- return err
- }
- {
- out, err := repo.Writer("infrastructure/pcloud-charts.yaml")
- if err != nil {
- return err
+ return repo.Atomic(func(r RepoFS) (string, error) {
+ if err := WriteYaml(r, "config.yaml", env); err != nil {
+ return "", err
}
- defer out.Close()
- _, err = out.Write([]byte(fmt.Sprintf(`
+ if err := WriteYaml(r, "env-cidrs.yaml", EnvCIDRs{}); err != nil {
+ return "", err
+ }
+ kust := NewKustomization()
+ kust.AddResources(
+ fmt.Sprintf("%s-flux", env.Name),
+ "infrastructure",
+ "environments",
+ )
+ if err := WriteYaml(r, "kustomization.yaml", kust); err != nil {
+ return "", err
+ }
+ {
+ out, err := r.Writer("infrastructure/pcloud-charts.yaml")
+ if err != nil {
+ return "", err
+ }
+ defer out.Close()
+ _, err = out.Write([]byte(fmt.Sprintf(`
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
@@ -455,25 +460,23 @@
ref:
branch: main
`, env.Name)))
- if err != nil {
- return err
+ if err != nil {
+ return "", err
+ }
}
- }
- infraKust := NewKustomization()
- infraKust.AddResources("pcloud-charts.yaml")
- if err := repo.WriteKustomization("infrastructure/kustomization.yaml", infraKust); err != nil {
- return err
- }
- if err := repo.WriteKustomization("environments/kustomization.yaml", NewKustomization()); err != nil {
- return err
- }
- if err := repo.CommitAndPush("initialize pcloud directory structure"); err != nil {
- return err
- }
- return nil
+ infraKust := NewKustomization()
+ infraKust.AddResources("pcloud-charts.yaml")
+ if err := WriteYaml(r, "infrastructure/kustomization.yaml", infraKust); err != nil {
+ return "", err
+ }
+ if err := WriteYaml(r, "environments/kustomization.yaml", NewKustomization()); err != nil {
+ return "", err
+ }
+ return "initialize pcloud directory structure", nil
+ })
}
-func (b Bootstrapper) installEnvManager(ss *soft.Client, repo RepoIO, nsGen NamespaceGenerator, nsCreator NamespaceCreator, env EnvConfig) error {
+func (b Bootstrapper) installEnvManager(repo RepoIO, ss *soft.Client, env EnvConfig) error {
keys, err := NewSSHKeyPair("env-manager")
if err != nil {
return err
@@ -485,18 +488,11 @@
if err := ss.MakeUserAdmin(user); err != nil {
return err
}
- appRepo := NewInMemoryAppRepository(CreateAllApps())
- app, err := appRepo.Find("env-manager")
+ app, err := b.appRepo.Find("env-manager")
if err != nil {
return err
}
- nms, err := nsGen.Generate(app.Namespace())
- if err != nil {
- return err
- }
- if err := nsCreator.Create(nms); err != nil {
- return err
- }
+ namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
derived := Derived{
Global: Values{
PCloudEnvName: env.Name,
@@ -508,100 +504,61 @@
"sshPrivateKey": string(keys.RawPrivateKey()),
},
}
- derived.Release.Namespace = nms
- return repo.InstallApp(app, filepath.Join("/infrastructure", app.Name()), derived.Values, derived)
+ return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, derived.Values, derived)
}
-func (b Bootstrapper) installOryHydraMaester(ss *soft.Client, repo RepoIO, nsGen NamespaceGenerator, nsCreator NamespaceCreator, env EnvConfig) error {
- appRepo := NewInMemoryAppRepository(CreateAllApps())
- app, err := appRepo.Find("hydra-maester")
+func (b Bootstrapper) installOryHydraMaester(repo RepoIO, env EnvConfig) error {
+ app, err := b.appRepo.Find("hydra-maester")
if err != nil {
return err
}
- nms, err := nsGen.Generate(app.Namespace())
- if err != nil {
- return err
- }
- if err := nsCreator.Create(nms); err != nil {
- return err
- }
+ namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
derived := Derived{
Global: Values{
PCloudEnvName: env.Name,
},
- Values: map[string]any{},
}
- derived.Release.Namespace = nms
- return repo.InstallApp(app, filepath.Join("/infrastructure", app.Name()), derived.Values, derived)
+ return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, nil, derived)
}
-func (b Bootstrapper) installDNSZoneManager(ss *soft.Client, repo RepoIO, nsGen NamespaceGenerator, nsCreator NamespaceCreator, env EnvConfig) error {
+func (b Bootstrapper) installDNSZoneManager(repo RepoIO, env EnvConfig) error {
const (
volumeClaimName = "dns-zone-configs"
volumeMountPath = "/etc/pcloud/dns-zone-configs"
)
- ns, err := nsGen.Generate("dns-zone-manager")
+ app, err := b.appRepo.Find("dns-zone-manager")
if err != nil {
return err
}
- if err := nsCreator.Create(ns); err != nil {
- return err
- }
- appRepo := NewInMemoryAppRepository(CreateAllApps())
- {
- app, err := appRepo.Find("dns-zone-manager")
- if err != nil {
- return err
- }
- derived := Derived{
- Global: Values{
- PCloudEnvName: env.Name,
- },
- Values: map[string]any{
- "volume": map[string]any{
- "claimName": volumeClaimName,
- "mountPath": volumeMountPath,
- "size": "1Gi",
- },
- "apiConfigMapName": dnsAPIConfigMapName,
- },
- Release: Release{
- Namespace: ns,
- },
- }
- if err := repo.InstallApp(app, filepath.Join("/infrastructure", app.Name()), derived.Values, derived); err != nil {
- return err
- }
- }
- return nil
-}
-
-func (b Bootstrapper) installFluxcdReconciler(ss *soft.Client, repo RepoIO, nsGen NamespaceGenerator, nsCreator NamespaceCreator, env EnvConfig) error {
- appRepo := NewInMemoryAppRepository(CreateAllApps())
- app, err := appRepo.Find("fluxcd-reconciler")
- if err != nil {
- return err
- }
- ns, err := nsGen.Generate(app.Namespace())
- if err != nil {
- return err
- }
- if err := nsCreator.Create(ns); err != nil {
- return err
- }
+ namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
derived := Derived{
Global: Values{
PCloudEnvName: env.Name,
},
- Values: map[string]any{},
- Release: Release{
- Namespace: ns,
+ Values: map[string]any{
+ "volume": map[string]any{
+ "claimName": volumeClaimName,
+ "mountPath": volumeMountPath,
+ "size": "1Gi",
+ },
+ "apiConfigMapName": dnsAPIConfigMapName,
},
}
- if err := repo.InstallApp(app, filepath.Join("/infrastructure", app.Name()), derived.Values, derived); err != nil {
+ return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, derived.Values, derived)
+}
+
+func (b Bootstrapper) installFluxcdReconciler(repo RepoIO, ss *soft.Client, env EnvConfig) error {
+ app, err := b.appRepo.Find("fluxcd-reconciler")
+ if err != nil {
return err
}
- return nil
+ namespace := fmt.Sprintf("%s-%s", env.Name, app.Namespace())
+ derived := Derived{
+ Global: Values{
+ PCloudEnvName: env.Name,
+ },
+ }
+ return InstallApp(repo, b.ns, app, filepath.Join("/infrastructure", app.Name()), namespace, nil, derived)
}
type HelmActionConfigFactory interface {
diff --git a/core/installer/cmd/app_manager.go b/core/installer/cmd/app_manager.go
index 47210fc..621ad87 100644
--- a/core/installer/cmd/app_manager.go
+++ b/core/installer/cmd/app_manager.go
@@ -72,12 +72,10 @@
return err
}
log.Println("Cloned repository")
- repoIO := installer.NewRepoIO(repo, signer)
- config, err := repoIO.ReadConfig()
+ repoIO, err := installer.NewRepoIO(repo, signer)
if err != nil {
return err
}
- log.Println("Read config")
kube, err := newNSCreator()
if err != nil {
return err
@@ -86,6 +84,11 @@
if err != nil {
return err
}
+ config, err := m.Config()
+ if err != nil {
+ return err
+ }
+ log.Println("Read config")
log.Println("Creating repository")
var r installer.AppRepository
if appManagerFlags.appRepoAddr != "" {
diff --git a/core/installer/cmd/bootstrap.go b/core/installer/cmd/bootstrap.go
index 9b68529..e6385b5 100644
--- a/core/installer/cmd/bootstrap.go
+++ b/core/installer/cmd/bootstrap.go
@@ -106,6 +106,7 @@
installer.NewFSChartLoader(bootstrapFlags.chartsDir),
nsCreator,
installer.NewActionConfigFactory(rootFlags.kubeConfig),
+ installer.NewInMemoryAppRepository(installer.CreateAllApps()),
)
return b.Run(envConfig)
}
diff --git a/core/installer/cmd/env_manager.go b/core/installer/cmd/env_manager.go
index 196110f..0142bee 100644
--- a/core/installer/cmd/env_manager.go
+++ b/core/installer/cmd/env_manager.go
@@ -64,7 +64,10 @@
return err
}
log.Printf("Cloned repo: %s\n", envManagerFlags.repoName)
- repoIO := installer.NewRepoIO(repo, sshKey.Signer())
+ repoIO, err := installer.NewRepoIO(repo, sshKey.Signer())
+ if err != nil {
+ return err
+ }
nsCreator, err := newNSCreator()
if err != nil {
return err
diff --git a/core/installer/cmd/welcome.go b/core/installer/cmd/welcome.go
index 43578b2..4390fcf 100644
--- a/core/installer/cmd/welcome.go
+++ b/core/installer/cmd/welcome.go
@@ -80,13 +80,17 @@
if err != nil {
return err
}
+ repoIO, err := installer.NewRepoIO(repo, signer)
+ if err != nil {
+ return err
+ }
nsCreator, err := newNSCreator()
if err != nil {
return err
}
s := welcome.NewServer(
welcomeFlags.port,
- installer.NewRepoIO(repo, signer),
+ repoIO,
nsCreator,
welcomeFlags.createAccountAddr,
welcomeFlags.loginAddr,
diff --git a/core/installer/derived.go b/core/installer/derived.go
new file mode 100644
index 0000000..813939e
--- /dev/null
+++ b/core/installer/derived.go
@@ -0,0 +1,168 @@
+package installer
+
+import (
+ "fmt"
+)
+
+type Release struct {
+ Namespace string `json:"namespace"`
+ RepoAddr string `json:"repoAddr"`
+ AppDir string `json:"appDir"`
+}
+
+type Derived struct {
+ Release Release `json:"release"`
+ Global Values `json:"global"`
+ Values map[string]any `json:"input"` // TODO(gio): rename to input
+}
+
+type Network struct {
+ Name string `json:"name,omitempty"`
+ IngressClass string `json:"ingressClass,omitempty"`
+ CertificateIssuer string `json:"certificateIssuer,omitempty"`
+ Domain string `json:"domain,omitempty"`
+ AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
+}
+
+type AppConfig struct {
+ Id string `json:"id"`
+ AppId string `json:"appId"`
+ Config map[string]any `json:"config"`
+ Derived Derived `json:"derived"`
+}
+
+func (a AppConfig) Input(schema Schema) map[string]any {
+ ret, err := derivedToConfig(a.Derived.Values, schema)
+ if err != nil {
+ panic(err) // TODO(gio): handle
+ }
+ return ret
+}
+
+func deriveValues(values any, schema Schema, networks []Network) (map[string]any, error) {
+ ret := make(map[string]any)
+ for k, def := range schema.Fields() {
+ // TODO(gio): validate that it is map
+ v, ok := values.(map[string]any)[k]
+ // TODO(gio): if missing use default value
+ if !ok {
+ if def.Kind() == KindSSHKey {
+ key, err := NewECDSASSHKeyPair("tmp")
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = map[string]string{
+ "public": string(key.RawAuthorizedKey()),
+ "private": string(key.RawPrivateKey()),
+ }
+ }
+ continue
+ }
+ switch def.Kind() {
+ case KindBoolean:
+ ret[k] = v
+ case KindString:
+ ret[k] = v
+ case KindInt:
+ ret[k] = v
+ case KindNetwork:
+ n, err := findNetwork(networks, v.(string)) // TODO(giolekva): validate
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = n
+ case KindAuth:
+ r, err := deriveValues(v, AuthSchema, networks)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
+ case KindSSHKey:
+ r, err := deriveValues(v, SSHKeySchema, networks)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
+ case KindStruct:
+ r, err := deriveValues(v, def, networks)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
+ default:
+ return nil, fmt.Errorf("Should not reach!")
+ }
+ }
+ return ret, nil
+}
+
+func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
+ ret := make(map[string]any)
+ for k, def := range schema.Fields() {
+ v, ok := derived[k]
+ // TODO(gio): if missing use default value
+ if !ok {
+ continue
+ }
+ switch def.Kind() {
+ case KindBoolean:
+ ret[k] = v
+ case KindString:
+ ret[k] = v
+ case KindInt:
+ ret[k] = v
+ case KindNetwork:
+ vm, ok := v.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ name, ok := vm["name"]
+ if !ok {
+ return nil, fmt.Errorf("expected network name")
+ }
+ ret[k] = name
+ case KindAuth:
+ vm, ok := v.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ r, err := derivedToConfig(vm, AuthSchema)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
+ case KindSSHKey:
+ vm, ok := v.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ r, err := derivedToConfig(vm, SSHKeySchema)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
+ case KindStruct:
+ vm, ok := v.(map[string]any)
+ if !ok {
+ return nil, fmt.Errorf("expected map")
+ }
+ r, err := derivedToConfig(vm, def)
+ if err != nil {
+ return nil, err
+ }
+ ret[k] = r
+ default:
+ return nil, fmt.Errorf("Should not reach!")
+ }
+ }
+ return ret, nil
+}
+
+func findNetwork(networks []Network, name string) (Network, error) {
+ for _, n := range networks {
+ if n.Name == name {
+ return n, nil
+ }
+ }
+ return Network{}, fmt.Errorf("Network not found: %s", name)
+}
diff --git a/core/installer/go.mod b/core/installer/go.mod
index 6371550..877a084 100644
--- a/core/installer/go.mod
+++ b/core/installer/go.mod
@@ -1,8 +1,10 @@
module github.com/giolekva/pcloud/core/installer
+replace github.com/giolekva/pcloud/installer => /Users/lekva/dev/src/pcloud/core/installer
+
go 1.21
-toolchain go1.21.5
+// toolchain go1.21.5
require (
cuelang.org/go v0.8.1
diff --git a/core/installer/kustomization.go b/core/installer/kustomization.go
index 1db9614..466c140 100644
--- a/core/installer/kustomization.go
+++ b/core/installer/kustomization.go
@@ -4,7 +4,6 @@
"bytes"
"golang.org/x/exp/slices"
"io"
- "io/ioutil"
"sigs.k8s.io/yaml"
)
@@ -23,18 +22,6 @@
}
}
-func ReadKustomization(r io.Reader) (*Kustomization, error) {
- contents, err := ioutil.ReadAll(r)
- if err != nil {
- return nil, err
- }
- var ret Kustomization
- if err = yaml.UnmarshalStrict(contents, &ret); err != nil {
- return nil, err
- }
- return &ret, nil
-}
-
func (k Kustomization) Write(w io.Writer) error {
contents, err := yaml.Marshal(k)
if err != nil {
diff --git a/core/installer/repoio.go b/core/installer/repoio.go
index 1d0bf7f..ffb4078 100644
--- a/core/installer/repoio.go
+++ b/core/installer/repoio.go
@@ -1,21 +1,16 @@
package installer
import (
- "bytes"
- "encoding/json"
"errors"
- "fmt"
"io"
"io/fs"
"io/ioutil"
"net"
- "net/http"
- "os"
- "path"
"path/filepath"
"sync"
"time"
+ "github.com/go-git/go-billy/v5"
"github.com/go-git/go-billy/v5/util"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
@@ -26,43 +21,74 @@
"github.com/giolekva/pcloud/core/installer/soft"
)
-type RepoIO interface {
- Addr() string
- Pull() error
- ReadConfig() (Config, error)
- ReadAppConfig(path string) (AppConfig, error)
- ReadKustomization(path string) (*Kustomization, error)
- WriteKustomization(path string, kust Kustomization) error
- ReadYaml(path string) (map[string]any, error)
- WriteYaml(path string, data any) error
- CommitAndPush(message string) error
- WriteCommitAndPush(path, contents, message string) error
+type RepoFS interface {
Reader(path string) (io.ReadCloser, error)
Writer(path string) (io.WriteCloser, error)
CreateDir(path string) error
RemoveDir(path string) error
- InstallApp(app App, path string, values map[string]any, derived Derived) error
- RemoveApp(path string) error
- FindAllInstances(root string, appId string) ([]AppConfig, error)
- FindInstance(root string, id string) (AppConfig, error)
+}
+
+type AtomicOp func(r RepoFS) (string, error)
+
+type RepoIO interface {
+ RepoFS
+ FullAddress() string
+ Pull() error
+ CommitAndPush(message string) error
+ Atomic(op AtomicOp) error
+}
+
+type repoFS struct {
+ fs billy.Filesystem
+}
+
+func (r *repoFS) Reader(path string) (io.ReadCloser, error) {
+ return r.fs.Open(path)
+}
+
+func (r *repoFS) Writer(path string) (io.WriteCloser, error) {
+ if err := r.fs.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
+ return nil, err
+ }
+ return r.fs.Create(path)
+}
+
+func (r *repoFS) CreateDir(path string) error {
+ return r.fs.MkdirAll(path, fs.ModePerm)
+}
+
+func (r *repoFS) RemoveDir(path string) error {
+ if err := util.RemoveAll(r.fs, path); err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil
+ }
+ return err
+ }
+ return nil
}
type repoIO struct {
+ *repoFS
repo *soft.Repository
signer ssh.Signer
l sync.Locker
}
-func NewRepoIO(repo *soft.Repository, signer ssh.Signer) RepoIO {
+func NewRepoIO(repo *soft.Repository, signer ssh.Signer) (RepoIO, error) {
+ wt, err := repo.Worktree()
+ if err != nil {
+ return nil, err
+ }
return &repoIO{
+ &repoFS{wt.Filesystem},
repo,
signer,
&sync.Mutex{},
- }
+ }, nil
}
-func (r *repoIO) Addr() string {
- return r.repo.Addr.Addr
+func (r *repoIO) FullAddress() string {
+ return r.repo.Addr.FullAddress()
}
func (r *repoIO) Pull() error {
@@ -74,121 +100,12 @@
func (r *repoIO) pullWithoutLock() error {
wt, err := r.repo.Worktree()
if err != nil {
- fmt.Printf("EEEER wt: %s\b", err)
return nil
}
- err = wt.Pull(&git.PullOptions{
+ return wt.Pull(&git.PullOptions{
Auth: auth(r.signer),
Force: true,
})
- // TODO(gio): propagate error
- if err != nil {
- fmt.Printf("EEEER: %s\b", err)
- }
- return nil
-}
-
-func (r *repoIO) ReadConfig() (Config, error) {
- configF, err := r.Reader(configFileName)
- if err != nil {
- return Config{}, err
- }
- defer configF.Close()
- var cfg Config
- if err := ReadYaml(configF, &cfg); err != nil {
- return Config{}, err
- } else {
- return cfg, nil
- }
-}
-
-func (r *repoIO) ReadAppConfig(path string) (AppConfig, error) {
- configF, err := r.Reader(path)
- if err != nil {
- return AppConfig{}, err
- }
- defer configF.Close()
- var cfg AppConfig
- if err := ReadYaml(configF, &cfg); err != nil {
- return AppConfig{}, err
- } else {
- return cfg, nil
- }
-}
-
-func (r *repoIO) ReadKustomization(path string) (*Kustomization, error) {
- inp, err := r.Reader(path)
- if err != nil {
- return nil, err
- }
- defer inp.Close()
- return ReadKustomization(inp)
-}
-
-func (r *repoIO) Reader(path string) (io.ReadCloser, error) {
- wt, err := r.repo.Worktree()
- if err != nil {
- return nil, err
- }
- return wt.Filesystem.Open(path)
-}
-
-func (r *repoIO) Writer(path string) (io.WriteCloser, error) {
- wt, err := r.repo.Worktree()
- if err != nil {
- return nil, err
- }
- if err := wt.Filesystem.MkdirAll(filepath.Dir(path), fs.ModePerm); err != nil {
- return nil, err
- }
- return wt.Filesystem.Create(path)
-}
-
-func (r *repoIO) WriteKustomization(path string, kust Kustomization) error {
- out, err := r.Writer(path)
- if err != nil {
- return err
- }
- return kust.Write(out)
-}
-
-func (r *repoIO) WriteYaml(path string, data any) error {
- out, err := r.Writer(path)
- if err != nil {
- return err
- }
- serialized, err := yaml.Marshal(data)
- if err != nil {
- return err
- }
- if _, err := out.Write(serialized); err != nil {
- return err
- }
- return nil
-}
-
-func (r *repoIO) ReadYaml(path string) (map[string]any, error) {
- inp, err := r.Reader(path)
- if err != nil {
- return nil, err
- }
- data := make(map[string]any)
- if err := ReadYaml(inp, &data); err != nil {
- return nil, err
- }
- return data, err
-}
-
-func (r *repoIO) WriteCommitAndPush(path, contents, message string) error {
- w, err := r.Writer(path)
- if err != nil {
- return err
- }
- defer w.Close()
- if _, err := io.WriteString(w, contents); err != nil {
- return err
- }
- return r.CommitAndPush(message)
}
func (r *repoIO) CommitAndPush(message string) error {
@@ -213,212 +130,17 @@
})
}
-func (r *repoIO) CreateDir(path string) error {
- wt, err := r.repo.Worktree()
- if err != nil {
- return err
- }
- return wt.Filesystem.MkdirAll(path, fs.ModePerm)
-}
-
-func (r *repoIO) RemoveDir(path string) error {
- wt, err := r.repo.Worktree()
- if err != nil {
- return err
- }
- err = util.RemoveAll(wt.Filesystem, path)
- if err == nil || errors.Is(err, fs.ErrNotExist) {
- return nil
- }
- return err
-}
-
-type Release struct {
- Namespace string `json:"namespace"`
- RepoAddr string `json:"repoAddr"`
- AppDir string `json:"appDir"`
-}
-
-type Derived struct {
- Release Release `json:"release"`
- Global Values `json:"global"`
- Values map[string]any `json:"input"` // TODO(gio): rename to input
-}
-
-type AppConfig struct {
- Id string `json:"id"`
- AppId string `json:"appId"`
- Config map[string]any `json:"config"`
- Derived Derived `json:"derived"`
-}
-
-func (a AppConfig) Input(schema Schema) map[string]any {
- ret, err := derivedToConfig(a.Derived.Values, schema)
- if err != nil {
- panic(err) // TODO(gio): handle
- }
- return ret
-}
-
-type allocatePortReq struct {
- Protocol string `json:"protocol"`
- SourcePort int `json:"sourcePort"`
- TargetService string `json:"targetService"`
- TargetPort int `json:"targetPort"`
-}
-
-// TODO(gio): most of this logic should move to AppManager
-func (r *repoIO) InstallApp(app App, appRootDir string, values map[string]any, derived Derived) error {
+func (r *repoIO) Atomic(op AtomicOp) error {
r.l.Lock()
defer r.l.Unlock()
if err := r.pullWithoutLock(); err != nil {
return err
}
- if !filepath.IsAbs(appRootDir) {
- return fmt.Errorf("Expected absolute path: %s", appRootDir)
- }
- derived.Release.RepoAddr = r.repo.Addr.FullAddress()
- // TODO(gio): maybe client should populate this?
- derived.Release.AppDir = appRootDir
- rendered, err := app.Render(derived)
- if err != nil {
+ if msg, err := op(r); err != nil {
return err
+ } else {
+ return r.CommitAndPush(msg)
}
- for _, p := range rendered.Ports {
- var buf bytes.Buffer
- req := allocatePortReq{
- Protocol: p.Protocol,
- SourcePort: p.SourcePort,
- TargetService: p.TargetService,
- TargetPort: p.TargetPort,
- }
- fmt.Printf("%+v\n", req)
- if err := json.NewEncoder(&buf).Encode(req); err != nil {
- return err
- }
- resp, err := http.Post(p.Allocator, "application/json", &buf)
- if err != nil {
- return err
- }
- if resp.StatusCode != http.StatusOK {
- io.Copy(os.Stdout, resp.Body)
- return fmt.Errorf("Could not allocate port %d, status code: %d", p.SourcePort, resp.StatusCode)
- }
- }
- if err := r.pullWithoutLock(); err != nil {
- return err
- }
- appRootDir = filepath.Clean(appRootDir)
- for p := appRootDir; p != "/"; {
- parent, child := filepath.Split(p)
- kustPath := filepath.Join(parent, "kustomization.yaml")
- kust, err := r.ReadKustomization(kustPath)
- if err != nil {
- if errors.Is(err, fs.ErrNotExist) {
- k := NewKustomization()
- kust = &k
- } else {
- return err
- }
- }
- kust.AddResources(child)
- if err := r.WriteKustomization(kustPath, *kust); err != nil {
- return err
- }
- p = filepath.Clean(parent)
- }
- {
- if err := r.RemoveDir(appRootDir); err != nil {
- return err
- }
- if err := r.CreateDir(appRootDir); err != nil {
- return err
- }
- cfg := AppConfig{
- AppId: app.Name(),
- Config: values,
- Derived: derived,
- }
- if err := r.WriteYaml(path.Join(appRootDir, configFileName), cfg); err != nil {
- return err
- }
- }
- {
- appKust := NewKustomization()
- for name, contents := range rendered.Resources {
- appKust.AddResources(name)
- out, err := r.Writer(path.Join(appRootDir, name))
- if err != nil {
- return err
- }
- defer out.Close()
- if _, err := out.Write(contents); err != nil {
- return err
- }
- }
- if err := r.WriteKustomization(path.Join(appRootDir, "kustomization.yaml"), appKust); err != nil {
- return err
- }
- }
- return r.CommitAndPush(fmt.Sprintf("install: %s", app.Name()))
-}
-
-func (r *repoIO) RemoveApp(appRootDir string) error {
- r.l.Lock()
- defer r.l.Unlock()
- r.RemoveDir(appRootDir)
- parent, child := filepath.Split(appRootDir)
- kustPath := filepath.Join(parent, "kustomization.yaml")
- kust, err := r.ReadKustomization(kustPath)
- if err != nil {
- return err
- }
- kust.RemoveResources(child)
- r.WriteKustomization(kustPath, *kust)
- return r.CommitAndPush(fmt.Sprintf("uninstall: %s", child))
-}
-
-func (r *repoIO) FindAllInstances(root string, name string) ([]AppConfig, error) {
- if !filepath.IsAbs(root) {
- return nil, fmt.Errorf("Expected absolute path: %s", root)
- }
- kust, err := r.ReadKustomization(filepath.Join(root, "kustomization.yaml"))
- if err != nil {
- return nil, err
- }
- ret := make([]AppConfig, 0)
- for _, app := range kust.Resources {
- cfg, err := r.ReadAppConfig(filepath.Join(root, app, "config.yaml"))
- if err != nil {
- return nil, err
- }
- cfg.Id = app
- if cfg.AppId == name {
- ret = append(ret, cfg)
- }
- }
- return ret, nil
-}
-
-func (r *repoIO) FindInstance(root string, id string) (AppConfig, error) {
- if !filepath.IsAbs(root) {
- return AppConfig{}, fmt.Errorf("Expected absolute path: %s", root)
- }
- kust, err := r.ReadKustomization(filepath.Join(root, "kustomization.yaml"))
- if err != nil {
- return AppConfig{}, err
- }
- for _, app := range kust.Resources {
- if app == id {
- cfg, err := r.ReadAppConfig(filepath.Join(root, app, "config.yaml"))
- if err != nil {
- return AppConfig{}, err
- }
- cfg.Id = id
- return cfg, nil
- }
- }
- return AppConfig{}, nil
}
func auth(signer ssh.Signer) *gitssh.PublicKeys {
@@ -434,7 +156,12 @@
}
}
-func ReadYaml[T any](r io.Reader, o *T) error {
+func ReadYaml[T any](repo RepoFS, path string, o *T) error {
+ r, err := repo.Reader(path)
+ if err != nil {
+ return err
+ }
+ defer r.Close()
if contents, err := ioutil.ReadAll(r); err != nil {
return err
} else {
@@ -442,138 +169,28 @@
}
}
-func deriveValues(values any, schema Schema, networks []Network) (map[string]any, error) {
- ret := make(map[string]any)
- for k, def := range schema.Fields() {
- // TODO(gio): validate that it is map
- v, ok := values.(map[string]any)[k]
- // TODO(gio): if missing use default value
- if !ok {
- if def.Kind() == KindSSHKey {
- key, err := NewECDSASSHKeyPair("tmp")
- if err != nil {
- return nil, err
- }
- ret[k] = map[string]string{
- "public": string(key.RawAuthorizedKey()),
- "private": string(key.RawPrivateKey()),
- }
- }
- continue
- }
- switch def.Kind() {
- case KindBoolean:
- ret[k] = v
- case KindString:
- ret[k] = v
- case KindInt:
- ret[k] = v
- case KindNetwork:
- n, err := findNetwork(networks, v.(string)) // TODO(giolekva): validate
- if err != nil {
- return nil, err
- }
- ret[k] = n
- case KindAuth:
- r, err := deriveValues(v, AuthSchema, networks)
- if err != nil {
- return nil, err
- }
- ret[k] = r
- case KindSSHKey:
- r, err := deriveValues(v, SSHKeySchema, networks)
- if err != nil {
- return nil, err
- }
- ret[k] = r
- case KindStruct:
- r, err := deriveValues(v, def, networks)
- if err != nil {
- return nil, err
- }
- ret[k] = r
- default:
- return nil, fmt.Errorf("Should not reach!")
- }
+func WriteYaml(repo RepoFS, path string, data any) error {
+ if d, ok := data.(*Kustomization); ok {
+ data = d
+ }
+ out, err := repo.Writer(path)
+ if err != nil {
+ return err
+ }
+ serialized, err := yaml.Marshal(data)
+ if err != nil {
+ return err
+ }
+ if _, err := out.Write(serialized); err != nil {
+ return err
+ }
+ return nil
+}
+
+func ReadKustomization(repo RepoFS, path string) (*Kustomization, error) {
+ ret := &Kustomization{}
+ if err := ReadYaml(repo, path, &ret); err != nil {
+ return nil, err
}
return ret, nil
}
-
-func derivedToConfig(derived map[string]any, schema Schema) (map[string]any, error) {
- ret := make(map[string]any)
- for k, def := range schema.Fields() {
- v, ok := derived[k]
- // TODO(gio): if missing use default value
- if !ok {
- continue
- }
- switch def.Kind() {
- case KindBoolean:
- ret[k] = v
- case KindString:
- ret[k] = v
- case KindInt:
- ret[k] = v
- case KindNetwork:
- vm, ok := v.(map[string]any)
- if !ok {
- return nil, fmt.Errorf("expected map")
- }
- name, ok := vm["name"]
- if !ok {
- return nil, fmt.Errorf("expected network name")
- }
- ret[k] = name
- case KindAuth:
- vm, ok := v.(map[string]any)
- if !ok {
- return nil, fmt.Errorf("expected map")
- }
- r, err := derivedToConfig(vm, AuthSchema)
- if err != nil {
- return nil, err
- }
- ret[k] = r
- case KindSSHKey:
- vm, ok := v.(map[string]any)
- if !ok {
- return nil, fmt.Errorf("expected map")
- }
- r, err := derivedToConfig(vm, SSHKeySchema)
- if err != nil {
- return nil, err
- }
- ret[k] = r
- case KindStruct:
- vm, ok := v.(map[string]any)
- if !ok {
- return nil, fmt.Errorf("expected map")
- }
- r, err := derivedToConfig(vm, def)
- if err != nil {
- return nil, err
- }
- ret[k] = r
- default:
- return nil, fmt.Errorf("Should not reach!")
- }
- }
- return ret, nil
-}
-
-func findNetwork(networks []Network, name string) (Network, error) {
- for _, n := range networks {
- if n.Name == name {
- return n, nil
- }
- }
- return Network{}, fmt.Errorf("Network not found: %s", name)
-}
-
-type Network struct {
- Name string `json:"name,omitempty"`
- IngressClass string `json:"ingressClass,omitempty"`
- CertificateIssuer string `json:"certificateIssuer,omitempty"`
- Domain string `json:"domain,omitempty"`
- AllocatePortAddr string `json:"allocatePortAddr,omitempty"`
-}
diff --git a/core/installer/tasks/activate.go b/core/installer/tasks/activate.go
index 1333f28..dffdfbe 100644
--- a/core/installer/tasks/activate.go
+++ b/core/installer/tasks/activate.go
@@ -8,6 +8,8 @@
"path"
"strings"
"text/template"
+
+ "github.com/giolekva/pcloud/core/installer"
)
//go:embed env-tmpl
@@ -29,45 +31,43 @@
return err
}
repoHost := strings.Split(st.ssClient.Addr, ":")[0]
- kust, err := st.repo.ReadKustomization("environments/kustomization.yaml")
- if err != nil {
- return err
- }
- kust.AddResources(env.Name)
- tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
- if err != nil {
- return err
- }
- var knownHosts bytes.Buffer
- for _, key := range ssPublicKeys {
- fmt.Fprintf(&knownHosts, "%s %s\n", repoHost, key)
- }
- for _, tmpl := range tmpls.Templates() { // TODO(gio): migrate to cue
- dstPath := path.Join("environments", env.Name, tmpl.Name())
- dst, err := st.repo.Writer(dstPath)
+ return st.repo.Atomic(func(r installer.RepoFS) (string, error) {
+ kust, err := installer.ReadKustomization(r, "environments/kustomization.yaml")
if err != nil {
- return err
+ return "", err
}
- defer dst.Close()
-
- if err := tmpl.Execute(dst, map[string]string{
- "Name": env.Name,
- "PrivateKey": base64.StdEncoding.EncodeToString(st.keys.RawPrivateKey()),
- "PublicKey": base64.StdEncoding.EncodeToString(st.keys.RawAuthorizedKey()),
- "RepoHost": repoHost,
- "RepoName": "config",
- "KnownHosts": base64.StdEncoding.EncodeToString(knownHosts.Bytes()),
- }); err != nil {
- return err
+ kust.AddResources(env.Name)
+ tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
+ if err != nil {
+ return "", err
}
- }
- if err := st.repo.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
- return err
- }
- if err := st.repo.CommitAndPush(fmt.Sprintf("%s: initialize environment", env.Name)); err != nil {
- return err
- }
- return nil
+ var knownHosts bytes.Buffer
+ for _, key := range ssPublicKeys {
+ fmt.Fprintf(&knownHosts, "%s %s\n", repoHost, key)
+ }
+ for _, tmpl := range tmpls.Templates() { // TODO(gio): migrate to cue
+ dstPath := path.Join("environments", env.Name, tmpl.Name())
+ dst, err := r.Writer(dstPath)
+ if err != nil {
+ return "", err
+ }
+ defer dst.Close()
+ if err := tmpl.Execute(dst, map[string]string{
+ "Name": env.Name,
+ "PrivateKey": base64.StdEncoding.EncodeToString(st.keys.RawPrivateKey()),
+ "PublicKey": base64.StdEncoding.EncodeToString(st.keys.RawAuthorizedKey()),
+ "RepoHost": repoHost,
+ "RepoName": "config",
+ "KnownHosts": base64.StdEncoding.EncodeToString(knownHosts.Bytes()),
+ }); err != nil {
+ return "", err
+ }
+ }
+ if err := installer.WriteYaml(r, "environments/kustomization.yaml", kust); err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%s: initialize environment", env.Name), nil
+ })
})
return &t
}
diff --git a/core/installer/tasks/dns.go b/core/installer/tasks/dns.go
index 13c4b58..65ef787 100644
--- a/core/installer/tasks/dns.go
+++ b/core/installer/tasks/dns.go
@@ -38,22 +38,26 @@
st *state,
) Task {
t := newLeafTask("Generate and publish DNS records", func() error {
+ key, err := newDNSSecKey(env.Domain)
+ if err != nil {
+ return err
+ }
repo, err := st.ssClient.GetRepo("config")
if err != nil {
return err
}
- r := installer.NewRepoIO(repo, st.ssClient.Signer)
- {
- key, err := newDNSSecKey(env.Domain)
- if err != nil {
- return err
- }
- out, err := r.Writer("dns-zone.yaml")
- if err != nil {
- return err
- }
- defer out.Close()
- dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
+ r, err := installer.NewRepoIO(repo, st.ssClient.Signer)
+ if err != nil {
+ return err
+ }
+ return r.Atomic(func(r installer.RepoFS) (string, error) {
+ {
+ out, err := r.Writer("dns-zone.yaml")
+ if err != nil {
+ return "", err
+ }
+ defer out.Close()
+ dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
apiVersion: dodo.cloud.dodo.cloud/v1
kind: DNSZone
metadata:
@@ -86,31 +90,29 @@
private: {{ .dnssec.Private | toString | b64enc }}
ds: {{ .dnssec.DS | toString | b64enc }}
`)
- if err != nil {
- return err
+ if err != nil {
+ return "", err
+ }
+ if err := dnsZoneTmpl.Execute(out, map[string]any{
+ "namespace": env.Name,
+ "zone": env.Domain,
+ "dnssec": key,
+ "publicIPs": st.publicIPs,
+ "ingressIP": ingressIP.String(),
+ }); err != nil {
+ return "", err
+ }
+ rootKust, err := installer.ReadKustomization(r, "kustomization.yaml")
+ if err != nil {
+ return "", err
+ }
+ rootKust.AddResources("dns-zone.yaml")
+ if err := installer.WriteYaml(r, "kustomization.yaml", rootKust); err != nil {
+ return "", err
+ }
+ return "configure dns zone", nil
}
- if err := dnsZoneTmpl.Execute(out, map[string]any{
- "namespace": env.Name,
- "zone": env.Domain,
- "dnssec": key,
- "publicIPs": st.publicIPs,
- "ingressIP": ingressIP.String(),
- }); err != nil {
- return err
- }
- rootKust, err := r.ReadKustomization("kustomization.yaml")
- if err != nil {
- return err
- }
- rootKust.AddResources("dns-zone.yaml")
- if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
- return err
- }
- if err := r.CommitAndPush("configure dns zone"); err != nil {
- return err
- }
- }
- return nil
+ })
})
return &t
}
diff --git a/core/installer/tasks/env.go b/core/installer/tasks/env.go
index 1d1eaba..7eaf0da 100644
--- a/core/installer/tasks/env.go
+++ b/core/installer/tasks/env.go
@@ -12,26 +12,25 @@
)
type state struct {
- infoListener EnvInfoListener
- publicIPs []net.IP
- nsCreator installer.NamespaceCreator
- repo installer.RepoIO
- ssAdminKeys *keygen.KeyPair
- ssClient *soft.Client
- fluxUserName string
- keys *keygen.KeyPair
- appManager *installer.AppManager
- appsRepo installer.AppRepository
- nsGen installer.NamespaceGenerator
- emptySuffixGen installer.SuffixGenerator
+ infoListener EnvInfoListener
+ publicIPs []net.IP
+ nsCreator installer.NamespaceCreator
+ repo installer.RepoIO
+ ssAdminKeys *keygen.KeyPair
+ ssClient *soft.Client
+ fluxUserName string
+ keys *keygen.KeyPair
+ appManager *installer.AppManager
+ appsRepo installer.AppRepository
}
type Env struct {
- PCloudEnvName string
- Name string
- ContactEmail string
- Domain string
- AdminPublicKey string
+ PCloudEnvName string
+ Name string
+ ContactEmail string
+ Domain string
+ AdminPublicKey string
+ NamespacePrefix string
}
type EnvInfoListener func(string)
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index 914307c..d718428 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -19,15 +19,16 @@
if err != nil {
return err
}
- r := installer.NewRepoIO(repo, st.ssClient.Signer)
+ r, err := installer.NewRepoIO(repo, st.ssClient.Signer)
+ if err != nil {
+ return err
+ }
appManager, err := installer.NewAppManager(r, st.nsCreator)
if err != nil {
return err
}
st.appManager = appManager
st.appsRepo = installer.NewInMemoryAppRepository(installer.CreateAllApps())
- st.nsGen = installer.NewPrefixGenerator(env.Name + "-")
- st.emptySuffixGen = installer.NewEmptySuffixGenerator()
return nil
})
t.beforeStart = func() {
@@ -56,28 +57,31 @@
if err != nil {
return err
}
- r := installer.NewRepoIO(repo, st.ssClient.Signer)
- {
- // TODO(giolekva): private domain can be configurable as well
- config := installer.Config{
- Values: installer.Values{
- PCloudEnvName: env.PCloudEnvName,
- Id: env.Name,
- ContactEmail: env.ContactEmail,
- Domain: env.Domain,
- PrivateDomain: fmt.Sprintf("p.%s", env.Domain),
- PublicIP: st.publicIPs[0].String(),
- NamespacePrefix: fmt.Sprintf("%s-", env.Name),
- },
- }
- if err := r.WriteYaml("config.yaml", config); err != nil {
- return err
- }
+ r, err := installer.NewRepoIO(repo, st.ssClient.Signer)
+ if err != nil {
+ return err
}
- {
+ r.Atomic(func(r installer.RepoFS) (string, error) {
+ {
+ // TODO(giolekva): private domain can be configurable as well
+ config := installer.Config{
+ Values: installer.Values{
+ PCloudEnvName: env.PCloudEnvName,
+ Id: env.Name,
+ ContactEmail: env.ContactEmail,
+ Domain: env.Domain,
+ PrivateDomain: fmt.Sprintf("p.%s", env.Domain),
+ PublicIP: st.publicIPs[0].String(),
+ NamespacePrefix: fmt.Sprintf("%s-", env.Name),
+ },
+ }
+ if err := installer.WriteYaml(r, "config.yaml", config); err != nil {
+ return "", err
+ }
+ }
out, err := r.Writer("pcloud-charts.yaml")
if err != nil {
- return err
+ return "", err
}
defer out.Close()
_, err = fmt.Fprintf(out, `
@@ -93,18 +97,18 @@
branch: ingress-port-allocator
`, env.Name)
if err != nil {
- return err
+ return "", err
}
- rootKust, err := r.ReadKustomization("kustomization.yaml")
+ rootKust, err := installer.ReadKustomization(r, "kustomization.yaml")
if err != nil {
- return err
+ return "", err
}
rootKust.AddResources("pcloud-charts.yaml")
- if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
- return err
+ if err := installer.WriteYaml(r, "kustomization.yaml", rootKust); err != nil {
+ return "", err
}
- r.CommitAndPush("configure charts repo")
- }
+ return "configure charts repo", nil
+ })
return nil
})
return &t
@@ -121,12 +125,17 @@
if err != nil {
return err
}
- r := installer.NewRepoIO(repo, st.ssClient.Signer)
- fa := firstAccount{false, initGroups}
- if err := r.WriteYaml("first-account.yaml", fa); err != nil {
+ r, err := installer.NewRepoIO(repo, st.ssClient.Signer)
+ if err != nil {
return err
}
- return r.CommitAndPush("first account membership configuration")
+ return r.Atomic(func(r installer.RepoFS) (string, error) {
+ fa := firstAccount{false, initGroups}
+ if err := installer.WriteYaml(r, "first-account.yaml", fa); err != nil {
+ return "", err
+ }
+ return "first account membership configuration", nil
+ })
})
return &t
}
@@ -161,32 +170,44 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
- "name": fmt.Sprintf("%s-ingress-private", env.Name),
- "from": ingressPrivateIP.String(),
- "to": ingressPrivateIP.String(),
- "autoAssign": false,
- "namespace": "metallb-system",
- }); err != nil {
- return err
+ {
+ appDir := fmt.Sprintf("/apps/%s-ingress-private", app.Name())
+ namespace := fmt.Sprintf("%s%s-ingress-private", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+ "name": fmt.Sprintf("%s-ingress-private", env.Name),
+ "from": ingressPrivateIP.String(),
+ "to": ingressPrivateIP.String(),
+ "autoAssign": false,
+ "namespace": "metallb-system",
+ }); err != nil {
+ return err
+ }
}
- if err := st.appManager.Install(app, st.nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
- "name": fmt.Sprintf("%s-headscale", env.Name),
- "from": headscaleIP.String(),
- "to": headscaleIP.String(),
- "autoAssign": false,
- "namespace": "metallb-system",
- }); err != nil {
- return err
+ {
+ appDir := fmt.Sprintf("/apps/%s-headscale", app.Name())
+ namespace := fmt.Sprintf("%s%s-ingress-private", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+ "name": fmt.Sprintf("%s-headscale", env.Name),
+ "from": headscaleIP.String(),
+ "to": headscaleIP.String(),
+ "autoAssign": false,
+ "namespace": "metallb-system",
+ }); err != nil {
+ return err
+ }
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
- "name": env.Name,
- "from": fromIP.String(),
- "to": toIP.String(),
- "autoAssign": false,
- "namespace": "metallb-system",
- }); err != nil {
- return err
+ {
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
+ "name": env.Name,
+ "from": fromIP.String(),
+ "to": toIP.String(),
+ "autoAssign": false,
+ "namespace": "metallb-system",
+ }); err != nil {
+ return err
+ }
}
}
{
@@ -205,7 +226,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"privateNetwork": map[string]any{
"hostname": "private-network-proxy",
"username": "private-network-proxy",
@@ -227,7 +250,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{}); err != nil {
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{}); err != nil {
return err
}
return nil
@@ -237,7 +262,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"apiConfigMap": map[string]any{
"name": "api-config", // TODO(gio): take from global pcloud config
"namespace": fmt.Sprintf("%s-dns-zone-manager", env.PCloudEnvName),
@@ -256,7 +283,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
}); err != nil {
return err
@@ -277,7 +306,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"authGroups": strings.Join(initGroups, ","),
}); err != nil {
return err
@@ -298,7 +329,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"subdomain": "headscale",
"ipSubnet": fmt.Sprintf("%s/24", startIP),
}); err != nil {
@@ -331,7 +364,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"repoAddr": st.ssClient.GetRepoAddress("config"),
"sshPrivateKey": string(keys.RawPrivateKey()),
}); err != nil {
@@ -364,7 +399,9 @@
if err != nil {
return err
}
- if err := st.appManager.Install(app, st.nsGen, st.emptySuffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s", app.Name())
+ namespace := fmt.Sprintf("%s%s", env.NamespacePrefix, app.Namespace())
+ if err := st.appManager.Install(app, appDir, namespace, map[string]any{
"repoAddr": st.ssClient.GetRepoAddress("config"),
"sshPrivateKey": string(keys.RawPrivateKey()),
"authGroups": strings.Join(initGroups, ","),
diff --git a/core/installer/tasks/init.go b/core/installer/tasks/init.go
index cb546c1..4ed7176 100644
--- a/core/installer/tasks/init.go
+++ b/core/installer/tasks/init.go
@@ -51,28 +51,18 @@
if err != nil {
return err
}
- ssValues := map[string]any{
- "privateKey": string(ssKeys.RawPrivateKey()),
- "publicKey": string(ssKeys.RawAuthorizedKey()),
- "adminKey": string(ssAdminKeys.RawAuthorizedKey()),
- }
derived := installer.Derived{
Global: installer.Values{
Id: env.Name,
PCloudEnvName: env.PCloudEnvName,
},
- Release: installer.Release{
- Namespace: env.Name,
+ Values: map[string]any{
+ "privateKey": string(ssKeys.RawPrivateKey()),
+ "publicKey": string(ssKeys.RawAuthorizedKey()),
+ "adminKey": string(ssAdminKeys.RawAuthorizedKey()),
},
- Values: ssValues,
}
- if err := st.nsCreator.Create(env.Name); err != nil {
- return err
- }
- if err := st.repo.InstallApp(ssApp, filepath.Join("/environments", env.Name, "config-repo"), ssValues, derived); err != nil {
- return err
- }
- return nil
+ return installer.InstallApp(st.repo, st.nsCreator, ssApp, filepath.Join("/environments", env.Name, "config-repo"), env.Name, derived.Values, derived)
})
return &t
}
@@ -116,24 +106,24 @@
if err != nil {
return err
}
- repoIO := installer.NewRepoIO(repo, st.ssClient.Signer)
- if err := func() error {
- w, err := repoIO.Writer("README.md")
+ repoIO, err := installer.NewRepoIO(repo, st.ssClient.Signer)
+ if err != nil {
+ return err
+ }
+ if err := repoIO.Atomic(func(r installer.RepoFS) (string, error) {
+ w, err := r.Writer("README.md")
if err != nil {
- return err
+ return "", err
}
defer w.Close()
if _, err := fmt.Fprintf(w, "# %s PCloud environment", env.Name); err != nil {
- return err
+ return "", err
}
- return nil
- }(); err != nil {
- return err
- }
- if err := repoIO.WriteKustomization("kustomization.yaml", installer.NewKustomization()); err != nil {
- return err
- }
- if err := repoIO.CommitAndPush("init"); err != nil {
+ if err := installer.WriteYaml(r, "kustomization.yaml", installer.NewKustomization()); err != nil {
+ return "", err
+ }
+ return "init", nil
+ }); err != nil {
return err
}
if err := st.ssClient.AddUser(st.fluxUserName, keys.AuthorizedKey()); err != nil {
diff --git a/core/installer/values-tmpl/headscale-user.cue b/core/installer/values-tmpl/headscale-user.cue
index fb38f8b..893ca28 100644
--- a/core/installer/values-tmpl/headscale-user.cue
+++ b/core/installer/values-tmpl/headscale-user.cue
@@ -22,7 +22,7 @@
}
helm: {
- "headscale-user": {
+ "headscale-user-\(input.username)": {
chart: charts.headscaleUser
values: {
username: input.username
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index 1206a73..b26a001 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -222,9 +222,14 @@
return err
}
log.Printf("Configuration: %+v\n", config)
- nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
- if err := s.m.Install(a, nsGen, suffixGen, values); err != nil {
+ suffix, err := suffixGen.Generate()
+ if err != nil {
+ return err
+ }
+ appDir := fmt.Sprintf("/apps/%s%s", a.Name(), suffix)
+ namespace := fmt.Sprintf("%s%s%s", config.Values.NamespacePrefix, a.Namespace(), suffix)
+ if err := s.m.Install(a, appDir, namespace, values); err != nil {
log.Printf("%s\n", err.Error())
return err
}
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
index e0d5a3f..3efe330 100644
--- a/core/installer/welcome/env.go
+++ b/core/installer/welcome/env.go
@@ -327,13 +327,7 @@
return
}
var env installer.EnvConfig
- cr, err := s.repo.Reader("config.yaml")
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- defer cr.Close()
- if err := installer.ReadYaml(cr, &env); err != nil {
+ if err := installer.ReadYaml(s.repo, "config.yaml", &env); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -348,13 +342,7 @@
req.Name = name
}
var cidrs installer.EnvCIDRs
- cidrsR, err := s.repo.Reader("env-cidrs.yaml")
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- defer cidrsR.Close()
- if err := installer.ReadYaml(cidrsR, &cidrs); err != nil {
+ if err := installer.ReadYaml(s.repo, "env-cidrs.yaml", &cidrs); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -364,7 +352,7 @@
return
}
cidrs = append(cidrs, installer.EnvCIDR{req.Name, startIP})
- if err := s.repo.WriteYaml("env-cidrs.yaml", cidrs); err != nil {
+ if err := installer.WriteYaml(s.repo, "env-cidrs.yaml", cidrs); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
@@ -385,11 +373,12 @@
}
t, dns := tasks.NewCreateEnvTask(
tasks.Env{
- PCloudEnvName: env.Name,
- Name: req.Name,
- ContactEmail: req.ContactEmail,
- Domain: req.Domain,
- AdminPublicKey: req.AdminPublicKey,
+ PCloudEnvName: env.Name,
+ Name: req.Name,
+ ContactEmail: req.ContactEmail,
+ Domain: req.Domain,
+ AdminPublicKey: req.AdminPublicKey,
+ NamespacePrefix: fmt.Sprintf("%s-", req.Name),
},
[]net.IP{
net.ParseIP("135.181.48.180"),
diff --git a/core/installer/welcome/welcome.go b/core/installer/welcome/welcome.go
index 9411747..697fd07 100644
--- a/core/installer/welcome/welcome.go
+++ b/core/installer/welcome/welcome.go
@@ -205,22 +205,16 @@
return
}
{
- config, err := s.repo.ReadConfig()
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
- nsGen := installer.NewPrefixGenerator(config.Values.NamespacePrefix)
- suffixGen := installer.NewFixedLengthRandomSuffixGenerator(3)
appManager, err := installer.NewAppManager(s.repo, s.nsCreator)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
+ config, err := appManager.Config()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
{
app, err := appsRepo.Find("headscale-user")
@@ -228,7 +222,9 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- if err := appManager.Install(app, nsGen, suffixGen, map[string]any{
+ appDir := fmt.Sprintf("/apps/%s-%s", app.Name(), req.Username)
+ namespace := fmt.Sprintf("%s%s", config.Values.NamespacePrefix, app.Namespace())
+ if err := appManager.Install(app, appDir, namespace, map[string]any{
"username": req.Username,
"preAuthKey": map[string]any{
"enabled": false,
@@ -253,28 +249,26 @@
}
func (s *Server) initMemberships(username string) error {
- inp, err := s.repo.Reader("first-account.yaml")
- if err != nil {
- return err
- }
- var fa firstaccount
- if err := installer.ReadYaml(inp, &fa); err != nil {
- return err
- }
- if fa.Created {
- return nil
- }
- req := initRequest{username, fa.Groups}
- var buf bytes.Buffer
- if err := json.NewEncoder(&buf).Encode(req); err != nil {
- return err
- }
- if _, err = http.Post(s.membershipsInitAddr, "applications/json", &buf); err != nil {
- return err
- }
- fa.Created = true
- if err := s.repo.WriteYaml("first-account.yaml", fa); err != nil {
- return err
- }
- return s.repo.CommitAndPush("initialized groups for first account")
+ return s.repo.Atomic(func(r installer.RepoFS) (string, error) {
+ var fa firstaccount
+ if err := installer.ReadYaml(r, "first-account.yaml", &fa); err != nil {
+ return "", err
+ }
+ if fa.Created {
+ return "", nil
+ }
+ req := initRequest{username, fa.Groups}
+ var buf bytes.Buffer
+ if err := json.NewEncoder(&buf).Encode(req); err != nil {
+ return "", err
+ }
+ if _, err := http.Post(s.membershipsInitAddr, "applications/json", &buf); err != nil {
+ return "", err
+ }
+ fa.Created = true
+ if err := installer.WriteYaml(r, "first-account.yaml", fa); err != nil {
+ return "", err
+ }
+ return "initialized groups for first account", nil
+ })
}