installer: helper leaf/parent tasks
diff --git a/core/installer/tasks/activate.go b/core/installer/tasks/activate.go
index f8b8018..f02e7a8 100644
--- a/core/installer/tasks/activate.go
+++ b/core/installer/tasks/activate.go
@@ -8,10 +8,6 @@
"path"
"strings"
"text/template"
-
- "github.com/charmbracelet/keygen"
-
- "github.com/giolekva/pcloud/core/installer"
)
//go:embed env-tmpl
@@ -24,76 +20,51 @@
}
func NewActivateEnvTask(env Env, st *state) Task {
- return &activateEnvTask{
- basicTask: basicTask{
- title: fmt.Sprintf("Activate %s environment", env.Name),
- },
- env: env,
- st: st,
- }
-}
-
-func (t *activateEnvTask) Start() {
- ssPublicKeys, err := t.st.ssClient.GetPublicKeys()
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- if err := t.addNewEnv(
- t.st.repo,
- strings.Split(t.st.ssClient.Addr, ":")[0],
- t.st.keys,
- ssPublicKeys,
- ); err != nil {
- t.callDoneListeners(err)
- return
- }
- t.callDoneListeners(nil)
-}
-
-func (t *activateEnvTask) addNewEnv(
- repoIO installer.RepoIO,
- repoHost string,
- keys *keygen.KeyPair,
- configRepoPublicKeys []string,
-) error {
- kust, err := repoIO.ReadKustomization("environments/kustomization.yaml")
- if err != nil {
- return err
- }
- kust.AddResources(t.env.Name)
- tmpls, err := template.ParseFS(filesTmpls, "env-tmpl/*.yaml")
- if err != nil {
- return err
- }
- var knownHosts bytes.Buffer
- for _, key := range configRepoPublicKeys {
- fmt.Fprintf(&knownHosts, "%s %s\n", repoHost, key)
- }
- for _, tmpl := range tmpls.Templates() {
- dstPath := path.Join("environments", t.env.Name, tmpl.Name())
- dst, err := repoIO.Writer(dstPath)
+ t := newLeafTask(fmt.Sprintf("Activate %s environment", env.Name), func() error {
+ ssPublicKeys, err := st.ssClient.GetPublicKeys()
if err != nil {
return err
}
- defer dst.Close()
-
- if err := tmpl.Execute(dst, map[string]string{
- "Name": t.env.Name,
- "PrivateKey": base64.StdEncoding.EncodeToString(keys.RawPrivateKey()),
- "PublicKey": base64.StdEncoding.EncodeToString(keys.RawAuthorizedKey()),
- "RepoHost": repoHost,
- "RepoName": "config",
- "KnownHosts": base64.StdEncoding.EncodeToString(knownHosts.Bytes()),
- }); err != nil {
+ repoHost := strings.Split(st.ssClient.Addr, ":")[0]
+ kust, err := st.repo.ReadKustomization("environments/kustomization.yaml")
+ if err != nil {
return err
}
- }
- if err := repoIO.WriteKustomization("environments/kustomization.yaml", *kust); err != nil {
- return err
- }
- if err := repoIO.CommitAndPush(fmt.Sprintf("%s: initialize environment", t.env.Name)); err != nil {
- return err
- }
- return nil
+ 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() {
+ dstPath := path.Join("environments", env.Name, tmpl.Name())
+ dst, err := st.repo.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 := 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
+ })
+ return &t
}
diff --git a/core/installer/tasks/dns.go b/core/installer/tasks/dns.go
index 60a8a9d..08cb863 100644
--- a/core/installer/tasks/dns.go
+++ b/core/installer/tasks/dns.go
@@ -2,7 +2,6 @@
import (
"context"
- "fmt"
"net"
"text/template"
"time"
@@ -12,54 +11,32 @@
"github.com/giolekva/pcloud/core/installer"
)
-type dnsResolver struct {
- basicTask
- name string
- expected []net.IP
- ctx context.Context
- env Env
- st *state
-}
+type Check func(ch Check) error
func NewDNSResolverTask(
name string,
expected []net.IP,
- ctx context.Context,
env Env,
st *state,
) Task {
- return &dnsResolver{
- basicTask: basicTask{
- title: "Configure DNS",
- },
- name: name,
- expected: expected,
- ctx: ctx,
- env: env,
- st: st,
- }
-}
-
-func (t *dnsResolver) Start() {
- repo, err := t.st.ssClient.GetRepo("config")
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- r := installer.NewRepoIO(repo, t.st.ssClient.Signer)
- {
- key, err := newDNSSecKey(t.env.Domain)
+ ctx := context.TODO()
+ t := newLeafTask("Configure DNS", func() error {
+ repo, err := st.ssClient.GetRepo("config")
if err != nil {
- t.callDoneListeners(err)
- return
+ return err
}
- out, err := r.Writer("dns-zone.yaml")
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- defer out.Close()
- dnsZoneTmpl, err := template.New("config").Funcs(sprig.TxtFuncMap()).Parse(`
+ 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(`
apiVersion: dodo.cloud.dodo.cloud/v1
kind: DNSZone
metadata:
@@ -92,58 +69,53 @@
private: {{ .dnssec.Private | toString | b64enc }}
ds: {{ .dnssec.DS | toString | b64enc }}
`)
- if err != nil {
- t.callDoneListeners(err)
- return
+ if err != nil {
+ return err
+ }
+ if err := dnsZoneTmpl.Execute(out, map[string]any{
+ "namespace": env.Name,
+ "zone": env.Domain,
+ "dnssec": key,
+ "publicIPs": st.publicIPs,
+ }); err != nil {
+ return err
+ }
+ rootKust := installer.NewKustomization()
+ rootKust.AddResources("dns-zone.yaml")
+ if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
+ return err
+ }
+ r.CommitAndPush("configure dns zone")
}
- if err := dnsZoneTmpl.Execute(out, map[string]any{
- "namespace": t.env.Name,
- "zone": t.env.Domain,
- "dnssec": key,
- "publicIPs": t.st.publicIPs,
- }); err != nil {
- t.callDoneListeners(err)
- return
- }
- rootKust := installer.NewKustomization()
- rootKust.AddResources("dns-zone.yaml")
- if err := r.WriteKustomization("kustomization.yaml", rootKust); err != nil {
- t.callDoneListeners(err)
- return
- }
- r.CommitAndPush("configure dns zone")
- }
- gotExpectedIPs := func(actual []net.IP) bool {
- for _, a := range actual {
- found := false
- for _, e := range t.expected {
- if a.Equal(e) {
- found = true
- break
+ gotExpectedIPs := func(actual []net.IP) bool {
+ for _, a := range actual {
+ found := false
+ for _, e := range expected {
+ if a.Equal(e) {
+ found = true
+ break
+ }
+ }
+ if !found {
+ return false
}
}
- if !found {
- return false
+ return true
+ }
+ check := func(check Check) error {
+ addrs, err := net.LookupIP(name)
+ if err == nil && gotExpectedIPs(addrs) {
+ return err
+ }
+ select {
+ case <-ctx.Done():
+ return nil
+ case <-time.After(5 * time.Second):
+ return check(check)
}
}
- return true
- }
- check := func(check Check) {
- addrs, err := net.LookupIP(t.name)
- if err == nil && gotExpectedIPs(addrs) {
- t.callDoneListeners(nil)
- return
- }
- select {
- case <-t.ctx.Done():
- t.callDoneListeners(fmt.Errorf("deadline exceeded"))
- return
- case <-time.After(5 * time.Second):
- check(check)
- }
- }
- check(check)
+ return check(check)
+ })
+ return &t
}
-
-type Check func(ch Check)
diff --git a/core/installer/tasks/dns_test.go b/core/installer/tasks/dns_test.go
deleted file mode 100644
index 270a495..0000000
--- a/core/installer/tasks/dns_test.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package tasks
-
-import (
- "context"
- "net"
- "testing"
- "time"
-)
-
-func TestGoogle(t *testing.T) {
- ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
- d := NewDNSResolverTask(
- "welcome.t5.lekva.me",
- []net.IP{
- net.ParseIP("135.181.48.180"),
- net.ParseIP("65.108.39.172"),
- },
- ctx,
- t.Logf,
- )
- d.FinalizeSubtasks()
- ch := make(chan struct{})
- d.OnDone(func(err error) {
- if err != nil {
- t.Logf("%s\n", err.Error())
- } else {
- t.Logf("Dooone")
- }
- ch <- struct{}{}
- })
- d.Start()
- <-ch
-}
diff --git a/core/installer/tasks/env.go b/core/installer/tasks/env.go
index 340648c..353eb4c 100644
--- a/core/installer/tasks/env.go
+++ b/core/installer/tasks/env.go
@@ -1,8 +1,6 @@
package tasks
import (
- "context"
- "fmt"
"net"
"github.com/charmbracelet/keygen"
@@ -20,13 +18,6 @@
keys *keygen.KeyPair
}
-type createEnvTask struct {
- basicTask
- env Env
- st state
- createConfigRepo Task
-}
-
type Env struct {
PCloudEnvName string
Name string
@@ -41,62 +32,18 @@
nsCreator installer.NamespaceCreator,
repo installer.RepoIO,
) Task {
- ctx := context.Background()
- e := &createEnvTask{
- basicTask: basicTask{
- title: fmt.Sprintf("Create %s environment", env.Domain),
- },
- env: env,
- st: state{
- publicIPs: publicIPs,
- nsCreator: nsCreator,
- repo: repo,
- },
+ st := state{
+ publicIPs: publicIPs,
+ nsCreator: nsCreator,
+ repo: repo,
}
- e.createConfigRepo = NewCreateConfigRepoTask(env, &e.st)
- e.AddSubtask(e.createConfigRepo)
- initRepo := NewInitConfigRepoTask(env, &e.st)
- e.AddSubtask(initRepo)
- e.createConfigRepo.OnDone(func(err error) {
- if err == nil {
- initRepo.Start()
- } else {
- e.callDoneListeners(err)
- }
- })
- activate := NewActivateEnvTask(env, &e.st)
- e.AddSubtask(activate)
- initRepo.OnDone(func(err error) {
- if err == nil {
- activate.Start()
- } else {
- e.callDoneListeners(err)
- }
- })
- dns := NewDNSResolverTask(env.Domain, publicIPs, ctx, env, &e.st)
- e.AddSubtask(dns)
- activate.OnDone(func(err error) {
- if err == nil {
- dns.Start()
- } else {
- e.callDoneListeners(err)
- }
- })
- setupInfra := NewSetupInfraAppsTask(env, &e.st)
- e.AddSubtask(setupInfra)
- dns.OnDone(func(err error) {
- if err == nil {
- setupInfra.Start()
- } else {
- e.callDoneListeners(err)
- }
- })
- setupInfra.OnDone(func(err error) {
- e.callDoneListeners(err)
- })
- return e
-}
-
-func (t *createEnvTask) Start() {
- go t.createConfigRepo.Start()
+ t := newSequentialParentTask(
+ "Create env",
+ NewCreateConfigRepoTask(env, &st),
+ NewInitConfigRepoTask(env, &st),
+ NewActivateEnvTask(env, &st),
+ NewDNSResolverTask(env.Domain, publicIPs, env, &st),
+ NewSetupInfraAppsTask(env, &st),
+ )
+ return &t
}
diff --git a/core/installer/tasks/infra.go b/core/installer/tasks/infra.go
index e57c789..06de0dc 100644
--- a/core/installer/tasks/infra.go
+++ b/core/installer/tasks/infra.go
@@ -16,29 +16,6 @@
st *state
}
-func NewSetupInfraAppsTask(env Env, st *state) Task {
- return &setupInfraAppsTask{
- basicTask: basicTask{
- title: "Configure environment infrastructure",
- },
- env: env,
- st: st,
- }
-}
-
-func (t *setupInfraAppsTask) Start() {
- repo, err := t.st.ssClient.GetRepo("config")
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- if err := t.initNewEnv(t.st.ssClient, installer.NewRepoIO(repo, t.st.ssClient.Signer), t.st.nsCreator, t.env.PCloudEnvName, t.st.publicIPs[0].String()); err != nil {
- t.callDoneListeners(err)
- return
- }
- t.callDoneListeners(nil)
-}
-
func (t *setupInfraAppsTask) initNewEnv(
ss *soft.Client,
r installer.RepoIO,
@@ -46,33 +23,43 @@
pcloudEnvName string,
pcloudPublicIP string,
) error {
- appManager, err := installer.NewAppManager(r, nsCreator)
- if err != nil {
- return err
- }
- appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
- // TODO(giolekva): private domain can be configurable as well
- config := installer.Config{
- Values: installer.Values{
- PCloudEnvName: pcloudEnvName,
- Id: t.env.Name,
- ContactEmail: t.env.ContactEmail,
- Domain: t.env.Domain,
- PrivateDomain: fmt.Sprintf("p.%s", t.env.Domain),
- PublicIP: pcloudPublicIP,
- NamespacePrefix: fmt.Sprintf("%s-", t.env.Name),
- },
- }
- if err := r.WriteYaml("config.yaml", config); err != nil {
- return err
- }
- {
- out, err := r.Writer("pcloud-charts.yaml")
+ return nil
+}
+
+func NewSetupInfraAppsTask(env Env, st *state) Task {
+ t := newLeafTask("Configure environment infrastructure", func() error {
+ repo, err := st.ssClient.GetRepo("config")
if err != nil {
return err
}
- defer out.Close()
- _, err = fmt.Fprintf(out, `
+ r := installer.NewRepoIO(repo, st.ssClient.Signer)
+ appManager, err := installer.NewAppManager(r, st.nsCreator)
+ if err != nil {
+ return err
+ }
+ appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
+ // 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
+ }
+ {
+ out, err := r.Writer("pcloud-charts.yaml")
+ if err != nil {
+ return err
+ }
+ defer out.Close()
+ _, err = fmt.Fprintf(out, `
apiVersion: source.toolkit.fluxcd.io/v1
kind: GitRepository
metadata:
@@ -83,167 +70,169 @@
url: https://github.com/giolekva/pcloud
ref:
branch: main
-`, t.env.Name)
+`, env.Name)
+ if err != nil {
+ return err
+ }
+ }
+ rootKust, err := r.ReadKustomization("kustomization.yaml")
if err != nil {
return err
}
- }
- rootKust, err := r.ReadKustomization("kustomization.yaml")
- if err != nil {
- return err
- }
- rootKust.AddResources("pcloud-charts.yaml")
- if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
- return err
- }
- r.CommitAndPush("configure charts repo")
- nsGen := installer.NewPrefixGenerator(t.env.Name + "-")
- emptySuffixGen := installer.NewEmptySuffixGenerator()
- ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
- if err != nil {
- return err
- }
- {
- headscaleIP := ingressPrivateIP.Next()
- app, err := appsRepo.Find("metallb-ipaddresspool")
+ rootKust.AddResources("pcloud-charts.yaml")
+ if err := r.WriteKustomization("kustomization.yaml", *rootKust); err != nil {
+ return err
+ }
+ r.CommitAndPush("configure charts repo")
+ nsGen := installer.NewPrefixGenerator(env.Name + "-")
+ emptySuffixGen := installer.NewEmptySuffixGenerator()
+ ingressPrivateIP, err := netip.ParseAddr("10.1.0.1")
if err != nil {
return err
}
- if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-ingress-private"), map[string]any{
- "Name": fmt.Sprintf("%s-ingress-private", t.env.Name),
- "From": ingressPrivateIP.String(),
- "To": ingressPrivateIP.String(),
- "AutoAssign": false,
- "Namespace": "metallb-system",
- }); err != nil {
- return err
+ {
+ headscaleIP := ingressPrivateIP.Next()
+ app, err := appsRepo.Find("metallb-ipaddresspool")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, 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
+ }
+ if err := appManager.Install(*app, 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
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+ "Name": env.Name,
+ "From": "10.1.0.100", // TODO(gio): auto-generate
+ "To": "10.1.0.254",
+ "AutoAssign": false,
+ "Namespace": "metallb-system",
+ }); err != nil {
+ return err
+ }
}
- if err := appManager.Install(*app, nsGen, installer.NewSuffixGenerator("-headscale"), map[string]any{
- "Name": fmt.Sprintf("%s-headscale", t.env.Name),
- "From": headscaleIP.String(),
- "To": headscaleIP.String(),
- "AutoAssign": false,
- "Namespace": "metallb-system",
- }); err != nil {
- return err
+ {
+ app, err := appsRepo.Find("private-network")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+ "PrivateNetwork": map[string]any{
+ "Hostname": "private-network-proxy",
+ "Username": "private-network-proxy",
+ "IPSubnet": "10.1.0.0/24",
+ },
+ }); err != nil {
+ return err
+ }
}
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "Name": t.env.Name,
- "From": "10.1.0.100", // TODO(gio): auto-generate
- "To": "10.1.0.254",
- "AutoAssign": false,
- "Namespace": "metallb-system",
- }); err != nil {
- return err
+ {
+ app, err := appsRepo.Find("certificate-issuer-public")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
+ return err
+ }
}
- }
- {
- app, err := appsRepo.Find("private-network")
- if err != nil {
- return err
+ {
+ app, err := appsRepo.Find("certificate-issuer-private")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, 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),
+ },
+ }); err != nil {
+ return err
+ }
}
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "PrivateNetwork": map[string]any{
- "Hostname": "private-network-proxy",
- "Username": "private-network-proxy",
- "IPSubnet": "10.1.0.0/24",
- },
- }); err != nil {
- return err
+ {
+ app, err := appsRepo.Find("core-auth")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+ "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
+ }); err != nil {
+ return err
+ }
}
- }
- {
- app, err := appsRepo.Find("certificate-issuer-public")
- if err != nil {
- return err
+ {
+ app, err := appsRepo.Find("headscale")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+ "Subdomain": "headscale",
+ }); err != nil {
+ return err
+ }
}
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{}); err != nil {
- return err
+ {
+ keys, err := installer.NewSSHKeyPair("welcome")
+ if err != nil {
+ return err
+ }
+ user := fmt.Sprintf("%s-welcome", env.Name)
+ if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
+ return err
+ }
+ if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
+ return err
+ }
+ app, err := appsRepo.Find("welcome")
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+ "RepoAddr": st.ssClient.GetRepoAddress("config"),
+ "SSHPrivateKey": string(keys.RawPrivateKey()),
+ }); err != nil {
+ return err
+ }
}
- }
- {
- app, err := appsRepo.Find("certificate-issuer-private")
- if err != nil {
- return err
+ {
+ user := fmt.Sprintf("%s-appmanager", env.Name)
+ keys, err := installer.NewSSHKeyPair(user)
+ if err != nil {
+ return err
+ }
+ if err := st.ssClient.AddUser(user, keys.AuthorizedKey()); err != nil {
+ return err
+ }
+ if err := st.ssClient.AddReadWriteCollaborator("config", user); err != nil {
+ return err
+ }
+ app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
+ if err != nil {
+ return err
+ }
+ if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
+ "RepoAddr": st.ssClient.GetRepoAddress("config"),
+ "SSHPrivateKey": string(keys.RawPrivateKey()),
+ }); err != nil {
+ return err
+ }
}
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "APIConfigMap": map[string]any{
- "Name": "api-config", // TODO(gio): take from global pcloud config
- "Namespace": fmt.Sprintf("%s-dns-zone-manager", pcloudEnvName),
- },
- }); err != nil {
- return err
- }
- }
- {
- app, err := appsRepo.Find("core-auth")
- if err != nil {
- return err
- }
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "Subdomain": "test", // TODO(giolekva): make core-auth chart actually use this
- }); err != nil {
- return err
- }
- }
- {
- app, err := appsRepo.Find("headscale")
- if err != nil {
- return err
- }
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "Subdomain": "headscale",
- }); err != nil {
- return err
- }
- }
- {
- keys, err := installer.NewSSHKeyPair("welcome")
- if err != nil {
- return err
- }
- user := fmt.Sprintf("%s-welcome", t.env.Name)
- if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
- return err
- }
- if err := ss.AddReadWriteCollaborator("config", user); err != nil {
- return err
- }
- app, err := appsRepo.Find("welcome")
- if err != nil {
- return err
- }
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "RepoAddr": ss.GetRepoAddress("config"),
- "SSHPrivateKey": string(keys.RawPrivateKey()),
- }); err != nil {
- return err
- }
- }
- {
- user := fmt.Sprintf("%s-appmanager", t.env.Name)
- keys, err := installer.NewSSHKeyPair(user)
- if err != nil {
- return err
- }
- if err := ss.AddUser(user, keys.AuthorizedKey()); err != nil {
- return err
- }
- if err := ss.AddReadWriteCollaborator("config", user); err != nil {
- return err
- }
- app, err := appsRepo.Find("app-manager") // TODO(giolekva): configure
- if err != nil {
- return err
- }
- if err := appManager.Install(*app, nsGen, emptySuffixGen, map[string]any{
- "RepoAddr": ss.GetRepoAddress("config"),
- "SSHPrivateKey": string(keys.RawPrivateKey()),
- }); err != nil {
- return err
- }
- }
- return nil
+ return nil
+ })
+ return &t
}
type DNSSecKey struct {
diff --git a/core/installer/tasks/init.go b/core/installer/tasks/init.go
index 1e4ada4..2457c4d 100644
--- a/core/installer/tasks/init.go
+++ b/core/installer/tasks/init.go
@@ -9,134 +9,95 @@
"github.com/giolekva/pcloud/core/installer/soft"
)
-type createConfigRepoTask struct {
- basicTask
- env Env
- st *state
-}
-
func NewCreateConfigRepoTask(env Env, st *state) Task {
- return &createConfigRepoTask{
- basicTask: basicTask{
- title: "Install Git server",
- },
- env: env,
- st: st,
- }
-}
-
-func (t *createConfigRepoTask) Start() {
- appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
- ssApp, err := appsRepo.Find("soft-serve")
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", t.env.Name))
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", t.env.Name))
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- ssValues := map[string]any{
- "ChartRepositoryNamespace": t.env.PCloudEnvName,
- "ServiceType": "ClusterIP",
- "PrivateKey": string(ssKeys.RawPrivateKey()),
- "PublicKey": string(ssKeys.RawAuthorizedKey()),
- "AdminKey": string(ssAdminKeys.RawAuthorizedKey()),
- "Ingress": map[string]any{
- "Enabled": false,
- },
- }
- derived := installer.Derived{
- Global: installer.Values{
- Id: t.env.Name,
- PCloudEnvName: t.env.PCloudEnvName,
- },
- Release: installer.Release{
- Namespace: t.env.Name,
- },
- Values: ssValues,
- }
- if err := t.st.nsCreator.Create(t.env.Name); err != nil {
- t.callDoneListeners(err)
- return
- }
- if err := t.st.repo.InstallApp(*ssApp, filepath.Join("/environments", t.env.Name, "config-repo"), ssValues, derived); err != nil {
- t.callDoneListeners(err)
- return
- }
- ssClient, err := soft.WaitForClient(
- fmt.Sprintf("soft-serve.%s.svc.cluster.local:%d", t.env.Name, 22),
- ssAdminKeys.RawPrivateKey(),
- log.Default())
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- if err := ssClient.AddPublicKey("admin", t.env.AdminPublicKey); err != nil {
- t.callDoneListeners(err)
- return
- }
- // // TODO(gio): defer?
- // // TODO(gio): remove at the end of final task cleanup
- // if err := ssClient.RemovePublicKey("admin", string(ssAdminKeys.RawAuthorizedKey())); err != nil {
- // t.callDoneListeners(err)
- // return
- // }
- t.st.ssClient = ssClient
- t.callDoneListeners(nil)
-}
-
-type initConfigRepoTask struct {
- basicTask
- env Env
- st *state
+ t := newLeafTask("Install Git server", func() error {
+ appsRepo := installer.NewInMemoryAppRepository(installer.CreateAllApps())
+ ssApp, err := appsRepo.Find("soft-serve")
+ if err != nil {
+ return err
+ }
+ ssAdminKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-admin-keys", env.Name))
+ if err != nil {
+ return err
+ }
+ ssKeys, err := installer.NewSSHKeyPair(fmt.Sprintf("%s-config-repo-keys", env.Name))
+ if err != nil {
+ return err
+ }
+ ssValues := map[string]any{
+ "ChartRepositoryNamespace": env.PCloudEnvName,
+ "ServiceType": "ClusterIP",
+ "PrivateKey": string(ssKeys.RawPrivateKey()),
+ "PublicKey": string(ssKeys.RawAuthorizedKey()),
+ "AdminKey": string(ssAdminKeys.RawAuthorizedKey()),
+ "Ingress": map[string]any{
+ "Enabled": false,
+ },
+ }
+ derived := installer.Derived{
+ Global: installer.Values{
+ Id: env.Name,
+ PCloudEnvName: env.PCloudEnvName,
+ },
+ Release: installer.Release{
+ Namespace: env.Name,
+ },
+ 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
+ }
+ ssClient, err := soft.WaitForClient(
+ fmt.Sprintf("soft-serve.%s.svc.cluster.local:%d", env.Name, 22),
+ ssAdminKeys.RawPrivateKey(),
+ log.Default())
+ if err != nil {
+ return err
+ }
+ if err := ssClient.AddPublicKey("admin", env.AdminPublicKey); err != nil {
+ return err
+ }
+ // // TODO(gio): defer?
+ // // TODO(gio): remove at the end of final task cleanup
+ // if err := ssClient.RemovePublicKey("admin", string(ssAdminKeys.RawAuthorizedKey())); err != nil {
+ // t.callDoneListeners(err)
+ // return
+ // }
+ st.ssClient = ssClient
+ return nil
+ })
+ return &t
}
func NewInitConfigRepoTask(env Env, st *state) Task {
- return &initConfigRepoTask{
- basicTask: basicTask{
- title: "Create Git repository for environment configuration",
- },
- env: env,
- st: st,
- }
-}
-
-func (t *initConfigRepoTask) Start() {
- t.st.fluxUserName = fmt.Sprintf("flux-%s", t.env.Name)
- keys, err := installer.NewSSHKeyPair(t.st.fluxUserName)
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- t.st.keys = keys
- if err := t.st.ssClient.AddRepository("config"); err != nil {
- t.callDoneListeners(err)
- return
- }
- repo, err := t.st.ssClient.GetRepo("config")
- if err != nil {
- t.callDoneListeners(err)
- return
- }
- repoIO := installer.NewRepoIO(repo, t.st.ssClient.Signer)
- if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", t.env.Name), "readme"); err != nil {
- t.callDoneListeners(err)
- return
- }
- if err := t.st.ssClient.AddUser(t.st.fluxUserName, keys.AuthorizedKey()); err != nil {
- t.callDoneListeners(err)
- return
- }
- if err := t.st.ssClient.AddReadOnlyCollaborator("config", t.st.fluxUserName); err != nil {
- t.callDoneListeners(err)
- return
- }
- t.callDoneListeners(nil)
+ t := newLeafTask("Create Git repository for environment configuration", func() error {
+ st.fluxUserName = fmt.Sprintf("flux-%s", env.Name)
+ keys, err := installer.NewSSHKeyPair(st.fluxUserName)
+ if err != nil {
+ return err
+ }
+ st.keys = keys
+ if err := st.ssClient.AddRepository("config"); err != nil {
+ return err
+ }
+ repo, err := st.ssClient.GetRepo("config")
+ if err != nil {
+ return err
+ }
+ repoIO := installer.NewRepoIO(repo, st.ssClient.Signer)
+ if err := repoIO.WriteCommitAndPush("README.md", fmt.Sprintf("# %s PCloud environment", env.Name), "readme"); err != nil {
+ return err
+ }
+ if err := st.ssClient.AddUser(st.fluxUserName, keys.AuthorizedKey()); err != nil {
+ return err
+ }
+ if err := st.ssClient.AddReadOnlyCollaborator("config", st.fluxUserName); err != nil {
+ return err
+ }
+ return nil
+ })
+ return &t
}
diff --git a/core/installer/tasks/tasks.go b/core/installer/tasks/tasks.go
index 4eb834d..9b0d533 100644
--- a/core/installer/tasks/tasks.go
+++ b/core/installer/tasks/tasks.go
@@ -1,9 +1,5 @@
package tasks
-import (
- "fmt"
-)
-
type Status int
const (
@@ -21,8 +17,6 @@
Status() Status
Err() error
Subtasks() []Task
- AddSubtask(t Task) error
- FinalizeSubtasks()
OnDone(l TaskDoneListener)
}
@@ -30,12 +24,18 @@
title string
status Status
err error
- subtasks []Task
- done []bool
- finalized bool
listeners []TaskDoneListener
}
+func newBasicTask(title string) basicTask {
+ return basicTask{
+ title: title,
+ status: StatusPending,
+ err: nil,
+ listeners: make([]TaskDoneListener, 0),
+ }
+}
+
func (b *basicTask) Title() string {
return b.title
}
@@ -48,47 +48,6 @@
return b.err
}
-func (b *basicTask) Subtasks() []Task {
- return b.subtasks
-}
-
-func (b *basicTask) AddSubtask(t Task) error {
- if b.finalized {
- return fmt.Errorf("already finalized")
- }
- i := len(b.subtasks)
- b.subtasks = append(b.subtasks, t)
- b.done = append(b.done, false)
- t.OnDone(func(err error) {
- if b.done[i] {
- panic(fmt.Sprintf("already done: %s", b.subtasks[i].Title()))
- }
- b.done[i] = true
- if err != nil {
- b.callDoneListeners(err)
- }
- if !b.finalized {
- return
- }
- done := 0
- for _, d := range b.done {
- if d {
- done++
- } else {
- break
- }
- }
- if done == len(b.subtasks) {
- b.callDoneListeners(nil)
- }
- })
- return nil
-}
-
-func (b *basicTask) FinalizeSubtasks() {
- b.finalized = true
-}
-
func (b *basicTask) OnDone(l TaskDoneListener) {
b.listeners = append(b.listeners, l)
}
@@ -104,3 +63,68 @@
b.err = err
}
}
+
+type leafTask struct {
+ basicTask
+ start func() error
+}
+
+func newLeafTask(title string, start func() error) leafTask {
+ return leafTask{
+ basicTask: newBasicTask(title),
+ start: start,
+ }
+}
+
+func (b *leafTask) Subtasks() []Task {
+ return make([]Task, 0)
+}
+
+func (b *leafTask) Start() {
+ b.callDoneListeners(b.start())
+}
+
+type parentTask struct {
+ leafTask
+ subtasks []Task
+}
+
+func newParentTask(title string, start func() error, subtasks ...Task) parentTask {
+ return parentTask{
+ leafTask: newLeafTask(title, start),
+ subtasks: subtasks,
+ }
+}
+
+func (t *parentTask) Subtasks() []Task {
+ return t.subtasks
+}
+
+type sequentialParentTask struct {
+ parentTask
+}
+
+func newSequentialParentTask(title string, subtasks ...Task) sequentialParentTask {
+ start := func() error {
+ errCh := make(chan error)
+ for i := range subtasks[:len(subtasks)-1] {
+ next := i + 1
+ subtasks[i].OnDone(func(err error) {
+ if err == nil {
+ go subtasks[next].Start()
+ } else {
+ errCh <- err
+ }
+ })
+ }
+ subtasks[len(subtasks)-1].OnDone(func(err error) {
+ errCh <- err
+ })
+ go subtasks[0].Start()
+ return <-errCh
+ }
+ t := sequentialParentTask{
+ parentTask: newParentTask(title, start, subtasks...),
+ }
+ return t
+}
diff --git a/core/installer/tasks/tasks_test.go b/core/installer/tasks/tasks_test.go
new file mode 100644
index 0000000..7aa78f3
--- /dev/null
+++ b/core/installer/tasks/tasks_test.go
@@ -0,0 +1,80 @@
+package tasks
+
+import (
+ "fmt"
+ "testing"
+)
+
+func TestLeaf(t *testing.T) {
+ l := newLeafTask("leaf", func() error {
+ return nil
+ })
+ done := make(chan error)
+ l.OnDone(func(err error) {
+ done <- err
+ })
+ go l.Start()
+ err := <-done
+ if err != nil {
+ t.Fatalf("Expected nil, got %s", err.Error())
+ }
+}
+
+func TestSequentialSuccess(t *testing.T) {
+ one := newLeafTask("one", func() error {
+ return nil
+ })
+ two := newLeafTask("two", func() error {
+ return nil
+ })
+ l := newSequentialParentTask("parent", &one, &two)
+ done := make(chan error)
+ l.OnDone(func(err error) {
+ done <- err
+ })
+ go l.Start()
+ err := <-done
+ if err != nil {
+ t.Fatalf("Expected nil, got %s", err.Error())
+ }
+}
+
+func TestSequentialFailsFirst(t *testing.T) {
+ one := newLeafTask("one", func() error {
+ return fmt.Errorf("one")
+ })
+ two := newLeafTask("two", func() error {
+ return nil
+ })
+ l := newSequentialParentTask("parent", &one, &two)
+ done := make(chan error)
+ l.OnDone(func(err error) {
+ done <- err
+ })
+ go l.Start()
+ err := <-done
+ if err == nil || err.Error() != "one" {
+ t.Fatalf("Expected one, got %s", err)
+ }
+}
+
+func TestSequentialFailsSecond(t *testing.T) {
+ one := newLeafTask("one", func() error {
+ fmt.Println("one")
+ return nil
+ })
+ two := newLeafTask("two", func() error {
+ fmt.Println("two")
+ return fmt.Errorf("two")
+ })
+ l := newSequentialParentTask("parent", &one, &two)
+ done := make(chan error)
+ l.OnDone(func(err error) {
+ done <- err
+ })
+ go l.Start()
+ err := <-done
+ if err == nil || err.Error() != "two" {
+ t.Fatalf("Expected two, got %s", err)
+ }
+}
diff --git a/core/installer/welcome/env-created.html b/core/installer/welcome/env-created.html
index 47eab2b..26f551a 100644
--- a/core/installer/welcome/env-created.html
+++ b/core/installer/welcome/env-created.html
@@ -20,7 +20,7 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
{{ if not (or (eq .Root.Status 2) (eq .Root.Status 3))}}
- <meta http-equiv="refresh" content="1000">
+ <meta http-equiv="refresh" content="2">
{{ end }}
</head>
<body>
diff --git a/core/installer/welcome/env.go b/core/installer/welcome/env.go
index bb8b6ff..9dd2abc 100644
--- a/core/installer/welcome/env.go
+++ b/core/installer/welcome/env.go
@@ -227,10 +227,10 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- if err := s.acceptInvitation(req.SecretToken); err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
+ // if err := s.acceptInvitation(req.SecretToken); err != nil {
+ // http.Error(w, err.Error(), http.StatusInternalServerError)
+ // return
+ // }
if name, err := s.nameGenerator.Generate(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -253,6 +253,6 @@
s.repo,
)
s.tasks["foo"] = t
- t.Start()
+ go t.Start()
http.Redirect(w, r, "/env/foo", http.StatusSeeOther)
}