AppManager: Run installation in background
Separates process into two sequential tasks: commit to config repo and
monitor release resources.
Change-Id: Ib208839dffc475b5d9c5d21758bc2a18a7f76cb7
diff --git a/core/installer/tasks/install.go b/core/installer/tasks/install.go
new file mode 100644
index 0000000..8b5bef7
--- /dev/null
+++ b/core/installer/tasks/install.go
@@ -0,0 +1,52 @@
+package tasks
+
+import (
+ "github.com/giolekva/pcloud/core/installer"
+)
+
+type InstallFunc func() (installer.ReleaseResources, error)
+
+type dynamicTaskSlice struct {
+ t []Task
+}
+
+func (d *dynamicTaskSlice) Tasks() []Task {
+ return d.t
+}
+
+func (d *dynamicTaskSlice) Append(t Task) {
+ d.t = append(d.t, t)
+}
+
+func NewInstallTask(mon installer.HelmReleaseMonitor, fn InstallFunc) Task {
+ d := &dynamicTaskSlice{t: []Task{}}
+ var rr installer.ReleaseResources
+ done := make(chan error)
+ installTask := newLeafTask("Downloading configuration files", func() error {
+ var err error
+ rr, err = fn()
+ return err
+ })
+ d.Append(&installTask)
+ installTask.OnDone(func(err error) {
+ if err != nil {
+ done <- err
+ return
+ }
+ monTasks := NewMonitorReleaseTasks(mon, rr)
+ for _, mt := range monTasks {
+ d.Append(mt)
+ }
+ monitor := newConcurrentParentTask("Monitor", true, monTasks...)
+ monitor.OnDone(func(err error) {
+ done <- err
+ })
+ monitor.Start()
+ })
+ start := func() error {
+ installTask.Start()
+ return <-done
+ }
+ t := newParentTask("Installing application", true, start, d)
+ return &t
+}
diff --git a/core/installer/tasks/release.go b/core/installer/tasks/release.go
index 229d76e..9a99698 100644
--- a/core/installer/tasks/release.go
+++ b/core/installer/tasks/release.go
@@ -6,12 +6,16 @@
"github.com/giolekva/pcloud/core/installer"
)
-func NewMonitorRelease(mon installer.HelmReleaseMonitor, rr installer.ReleaseResources) Task {
+func NewMonitorReleaseTasks(mon installer.HelmReleaseMonitor, rr installer.ReleaseResources) []Task {
var t []Task
for _, h := range rr.Helm {
t = append(t, newMonitorHelm(mon, h))
}
- return newConcurrentParentTask("Monitor", true, t...)
+ return t
+}
+
+func NewMonitorRelease(mon installer.HelmReleaseMonitor, rr installer.ReleaseResources) Task {
+ return newConcurrentParentTask("Monitor", true, NewMonitorReleaseTasks(mon, rr)...)
}
func newMonitorHelm(mon installer.HelmReleaseMonitor, h installer.Resource) Task {
diff --git a/core/installer/tasks/tasks.go b/core/installer/tasks/tasks.go
index 3db7042..69ddd17 100644
--- a/core/installer/tasks/tasks.go
+++ b/core/installer/tasks/tasks.go
@@ -15,6 +15,10 @@
type TaskDoneListener func(err error)
+type Subtasks interface {
+ Tasks() []Task
+}
+
type Task interface {
Title() string
Start()
@@ -103,11 +107,17 @@
type parentTask struct {
leafTask
- subtasks []Task
+ subtasks Subtasks
showChildren bool
}
-func newParentTask(title string, showChildren bool, start func() error, subtasks ...Task) parentTask {
+type TaskSlice []Task
+
+func (s TaskSlice) Tasks() []Task {
+ return s
+}
+
+func newParentTask(title string, showChildren bool, start func() error, subtasks Subtasks) parentTask {
return parentTask{
leafTask: newLeafTask(title, start),
subtasks: subtasks,
@@ -117,17 +127,13 @@
func (t *parentTask) Subtasks() []Task {
if t.showChildren {
- return t.subtasks
+ return t.subtasks.Tasks()
} else {
return make([]Task, 0)
}
}
-type sequentialParentTask struct {
- parentTask
-}
-
-func newSequentialParentTask(title string, showChildren bool, subtasks ...Task) *sequentialParentTask {
+func newSequentialParentTask(title string, showChildren bool, subtasks ...Task) *parentTask {
start := func() error {
errCh := make(chan error)
for i := range subtasks[:len(subtasks)-1] {
@@ -146,16 +152,11 @@
go subtasks[0].Start()
return <-errCh
}
- return &sequentialParentTask{
- parentTask: newParentTask(title, showChildren, start, subtasks...),
- }
+ t := newParentTask(title, showChildren, start, TaskSlice(subtasks))
+ return &t
}
-type concurrentParentTask struct {
- parentTask
-}
-
-func newConcurrentParentTask(title string, showChildren bool, subtasks ...Task) *concurrentParentTask {
+func newConcurrentParentTask(title string, showChildren bool, subtasks ...Task) *parentTask {
start := func() error {
errCh := make(chan error)
for i := range subtasks {
@@ -177,7 +178,6 @@
}
return nil
}
- return &concurrentParentTask{
- parentTask: newParentTask(title, showChildren, start, subtasks...),
- }
+ t := newParentTask(title, showChildren, start, TaskSlice(subtasks))
+ return &t
}
diff --git a/core/installer/welcome/appmanager-tmpl/app.html b/core/installer/welcome/appmanager-tmpl/app.html
index e6fe096..b25f5b1 100644
--- a/core/installer/welcome/appmanager-tmpl/app.html
+++ b/core/installer/welcome/appmanager-tmpl/app.html
@@ -120,9 +120,12 @@
{{ end }}
{{ define "extra_menu" }}
- <li><a href="/app/{{ .App.Slug }}" class="{{ if eq $.CurrentPage .App.Name }}primary{{ end }}">{{ .App.Name }}</a></li>
+ <li><a href="/app/{{ .App.Slug }}" {{ if eq $.CurrentPage .App.Name }}class="primary"{{ end }}>{{ .App.Name }}</a></li>
+ {{ if (and (not $.Instance) $.Task) }}
+ <li><a href="/instance/{{ $.CurrentPage }}" class="primary">{{ $.CurrentPage }}</a></li>
+ {{ end }}
{{ range .Instances }}
- <li><a href="/instance/{{ .Id }}" class="{{ if eq $.CurrentPage .Id }}primary{{ end }}">{{ .Id }}</a></li>
+ <li><a href="/instance/{{ .Id }}" {{ if eq $.CurrentPage .Id }}class="primary"{{ end }}>{{ .Id }}</a></li>
{{ end }}
{{ end }}
@@ -135,7 +138,7 @@
{{ if .Task }}
{{if or (eq .Task.Status 0) (eq .Task.Status 1) }}
{{ $renderForm = false }}
- Waiting for resources:
+ Installation in progress (feel free to navigate away from this page):
<ul class="progress">
{{ template "task" .Task.Subtasks }}
</ul>
diff --git a/core/installer/welcome/appmanager.go b/core/installer/welcome/appmanager.go
index 29d19ed..345cbab 100644
--- a/core/installer/welcome/appmanager.go
+++ b/core/installer/welcome/appmanager.go
@@ -28,6 +28,7 @@
reconciler tasks.Reconciler
h installer.HelmReleaseMonitor
tasks map[string]tasks.Task
+ ta map[string]installer.EnvApp
tmpl tmplts
}
@@ -77,6 +78,7 @@
reconciler: reconciler,
h: h,
tasks: make(map[string]tasks.Task),
+ ta: make(map[string]installer.EnvApp),
tmpl: tmpl,
}, nil
}
@@ -236,21 +238,20 @@
instanceId := a.Slug() + suffix
appDir := fmt.Sprintf("/apps/%s", instanceId)
namespace := fmt.Sprintf("%s%s%s", env.NamespacePrefix, a.Namespace(), suffix)
- rr, err := s.m.Install(a, instanceId, appDir, namespace, values)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
- }
+ t := tasks.NewInstallTask(s.h, func() (installer.ReleaseResources, error) {
+ return s.m.Install(a, instanceId, appDir, namespace, values)
+ })
ctx, _ := context.WithTimeout(context.Background(), 2*time.Minute)
go s.reconciler.Reconcile(ctx)
if _, ok := s.tasks[instanceId]; ok {
panic("MUST NOT REACH!")
}
- t := tasks.NewMonitorRelease(s.h, rr)
+ s.tasks[instanceId] = t
+ s.ta[instanceId] = a
t.OnDone(func(err error) {
delete(s.tasks, instanceId)
+ delete(s.ta, instanceId)
})
- s.tasks[instanceId] = t
go t.Start()
if _, err := fmt.Fprintf(w, "/instance/%s", instanceId); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -422,15 +423,25 @@
http.Error(w, "empty slug", http.StatusBadRequest)
return
}
+ t, ok := s.tasks[slug]
instance, err := s.m.FindInstance(slug)
- if err != nil {
+ if err != nil && !ok {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- a, err := s.m.GetInstanceApp(instance.Id)
- if err != nil {
- http.Error(w, err.Error(), http.StatusInternalServerError)
- return
+ var a installer.EnvApp
+ if instance != nil {
+ a, err = s.m.GetInstanceApp(instance.Id)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ } else {
+ var ok bool
+ a, ok = s.ta[slug]
+ if !ok {
+ panic("MUST NOT REACH!")
+ }
}
instances, err := s.m.FindAllAppInstances(a.Slug())
if err != nil {
@@ -442,14 +453,13 @@
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- t := s.tasks[slug]
data := appPageData{
App: a,
Instance: instance,
Instances: instances,
AvailableNetworks: networks,
Task: t,
- CurrentPage: instance.Id,
+ CurrentPage: slug,
}
if err := s.tmpl.app.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)