DodoApp: Implement commit status page
Render used volume, postgresql and ingress resource details.
Change-Id: I87f34fd19d0d0d31ec495d2798c9f5ce99c0fd43
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 7c35d69..43a6bd7 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -42,11 +42,13 @@
apiCreateApp = "/api/apps"
sessionCookie = "dodo-app-session"
userCtx = "user"
+ initCommitMsg = "init"
)
type dodoAppTmplts struct {
- index *template.Template
- appStatus *template.Template
+ index *template.Template
+ appStatus *template.Template
+ commitStatus *template.Template
}
func parseTemplatesDodoApp(fs embed.FS) (dodoAppTmplts, error) {
@@ -69,7 +71,11 @@
if err != nil {
return dodoAppTmplts{}, err
}
- return dodoAppTmplts{index, appStatus}, nil
+ commitStatus, err := parse("dodo-app-tmpl/commit_status.html")
+ if err != nil {
+ return dodoAppTmplts{}, err
+ }
+ return dodoAppTmplts{index, appStatus, commitStatus}, nil
}
type DodoAppServer struct {
@@ -185,6 +191,7 @@
r.HandleFunc(apiCreateApp, s.handleAPICreateApp).Methods(http.MethodPost)
r.HandleFunc("/{app-name}"+loginPath, s.handleLoginForm).Methods(http.MethodGet)
r.HandleFunc("/{app-name}"+loginPath, s.handleLogin).Methods(http.MethodPost)
+ r.HandleFunc("/{app-name}/{hash}", s.handleAppCommit).Methods(http.MethodGet)
r.HandleFunc("/{app-name}", s.handleAppStatus).Methods(http.MethodGet)
r.HandleFunc("/", s.handleStatus).Methods(http.MethodGet)
r.HandleFunc("/", s.handleCreateApp).Methods(http.MethodPost)
@@ -373,10 +380,16 @@
http.Redirect(w, r, fmt.Sprintf("/%s", appName), http.StatusSeeOther)
}
+type navItem struct {
+ Name string
+ Address string
+}
+
type statusData struct {
- Apps []string
- Networks []installer.Network
- Types []string
+ Navigation []navItem
+ Apps []string
+ Networks []installer.Network
+ Types []string
}
func (s *DodoAppServer) handleStatus(w http.ResponseWriter, r *http.Request) {
@@ -399,7 +412,8 @@
for _, t := range s.appTmpls.Types() {
types = append(types, strings.Replace(t, "-", ":", 1))
}
- data := statusData{apps, networks, types}
+ n := []navItem{navItem{"Home", "/"}}
+ data := statusData{n, apps, networks, types}
if err := s.tmplts.index.Execute(w, data); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
@@ -407,9 +421,10 @@
}
type appStatusData struct {
+ Navigation []navItem
Name string
GitCloneCommand string
- Commits []Commit
+ Commits []CommitMeta
}
func (s *DodoAppServer) handleAppStatus(w http.ResponseWriter, r *http.Request) {
@@ -444,6 +459,10 @@
return
}
data := appStatusData{
+ Navigation: []navItem{
+ navItem{"Home", "/"},
+ navItem{appName, "/" + appName},
+ },
Name: appName,
GitCloneCommand: fmt.Sprintf("git clone %s/%s\n\n\n", s.repoPublicAddr, appName),
Commits: commits,
@@ -454,6 +473,97 @@
}
}
+type volume struct {
+ Name string
+ Size string
+}
+
+type postgresql struct {
+ Name string
+ Version string
+ Volume string
+}
+
+type ingress struct {
+ Host string
+}
+
+type resourceData struct {
+ Volume []volume
+ PostgreSQL []postgresql
+ Ingress []ingress
+}
+
+type commitStatusData struct {
+ Navigation []navItem
+ AppName string
+ Commit Commit
+ Resources resourceData
+}
+
+func (s *DodoAppServer) handleAppCommit(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ appName, ok := vars["app-name"]
+ if !ok || appName == "" {
+ http.Error(w, "missing app-name", http.StatusBadRequest)
+ return
+ }
+ hash, ok := vars["hash"]
+ if !ok || appName == "" {
+ http.Error(w, "missing app-name", http.StatusBadRequest)
+ return
+ }
+ u := r.Context().Value(userCtx)
+ if u == nil {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+ user, ok := u.(string)
+ if !ok {
+ http.Error(w, "could not get user", http.StatusInternalServerError)
+ return
+ }
+ owner, err := s.st.GetAppOwner(appName)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ if owner != user {
+ http.Error(w, "unauthorized", http.StatusUnauthorized)
+ return
+ }
+ commit, err := s.st.GetCommit(hash)
+ if err != nil {
+ // TODO(gio): not-found ?
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ var res strings.Builder
+ if err := json.NewEncoder(&res).Encode(commit.Resources.Helm); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ resData, err := extractResourceData(commit.Resources.Helm)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ data := commitStatusData{
+ Navigation: []navItem{
+ navItem{"Home", "/"},
+ navItem{appName, "/" + appName},
+ navItem{hash, "/" + appName + "/" + hash},
+ },
+ AppName: appName,
+ Commit: commit,
+ Resources: resData,
+ }
+ if err := s.tmplts.commitStatus.Execute(w, data); err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+}
+
type apiUpdateReq struct {
Ref string `json:"ref"`
Repository struct {
@@ -508,16 +618,11 @@
fmt.Printf("Error: could not find commit message")
return
}
- if err := s.updateDodoApp(instanceAppStatus, req.Repository.Name, s.appConfigs[req.Repository.Name].Namespace, networks); err != nil {
+ resources, err := s.updateDodoApp(instanceAppStatus, req.Repository.Name, s.appConfigs[req.Repository.Name].Namespace, networks)
+ if err = s.createCommit(req.Repository.Name, req.After, commitMsg, err, resources); err != nil {
fmt.Printf("Error: %s\n", err.Error())
- if err := s.st.CreateCommit(req.Repository.Name, req.After, commitMsg, err.Error()); err != nil {
- fmt.Printf("Error: %s\n", err.Error())
- }
return
}
- if err := s.st.CreateCommit(req.Repository.Name, req.After, commitMsg, "OK"); err != nil {
- fmt.Printf("Error: %s\n", err.Error())
- }
for addr, _ := range s.workers[req.Repository.Name] {
go func() {
// TODO(gio): make port configurable
@@ -710,7 +815,8 @@
if err != nil {
return err
}
- if err := s.initRepo(appRepo, appType, n, subdomain); err != nil {
+ commit, err := s.initRepo(appRepo, appType, n, subdomain)
+ if err != nil {
return err
}
apps := installer.NewInMemoryAppRepository(installer.CreateAllApps())
@@ -729,7 +835,12 @@
}
namespace := fmt.Sprintf("%s%s%s", s.env.NamespacePrefix, instanceApp.Namespace(), suffix)
s.appConfigs[appName] = appConfig{namespace, network}
- if err := s.updateDodoApp(instanceAppStatus, appName, namespace, networks); err != nil {
+ resources, err := s.updateDodoApp(instanceAppStatus, appName, namespace, networks)
+ if err != nil {
+ return err
+ }
+ if err = s.createCommit(appName, commit, initCommitMsg, err, resources); err != nil {
+ fmt.Printf("Error: %s\n", err.Error())
return err
}
configRepo, err := s.client.GetRepo(ConfigRepoName)
@@ -741,7 +852,7 @@
if err != nil {
return err
}
- if err := configRepo.Do(func(fs soft.RepoFS) (string, error) {
+ _, err = configRepo.Do(func(fs soft.RepoFS) (string, error) {
w, err := fs.Writer(appConfigsFile)
if err != nil {
return "", err
@@ -768,7 +879,8 @@
return "", err
}
return fmt.Sprintf("Installed app: %s", appName), nil
- }); err != nil {
+ })
+ if err != nil {
return err
}
cfg, err := m.FindInstance(appName)
@@ -849,32 +961,33 @@
} `json:"input"`
}
-func (s *DodoAppServer) updateDodoApp(appStatus installer.EnvApp, name, namespace string, networks []installer.Network) error {
+func (s *DodoAppServer) updateDodoApp(appStatus installer.EnvApp, name, namespace string, networks []installer.Network) (installer.ReleaseResources, error) {
fmt.Println("111")
repo, err := s.client.GetRepo(name)
if err != nil {
- return err
+ return installer.ReleaseResources{}, err
}
fmt.Println("111")
hf := installer.NewGitHelmFetcher()
m, err := installer.NewAppManager(repo, s.nsc, s.jc, hf, "/.dodo")
if err != nil {
- return err
+ return installer.ReleaseResources{}, err
}
fmt.Println("111")
appCfg, err := soft.ReadFile(repo, "app.cue")
if err != nil {
- return err
+ return installer.ReleaseResources{}, err
}
fmt.Println("111")
app, err := installer.NewDodoApp(appCfg)
if err != nil {
- return err
+ return installer.ReleaseResources{}, err
}
fmt.Println("111")
lg := installer.GitRepositoryLocalChartGenerator{"app", namespace}
- return repo.Do(func(r soft.RepoFS) (string, error) {
- res, err := m.Install(
+ var ret installer.ReleaseResources
+ if _, err := repo.Do(func(r soft.RepoFS) (string, error) {
+ ret, err = m.Install(
app,
"app",
"/.dodo/app",
@@ -898,7 +1011,7 @@
}
fmt.Println("111")
var rendered dodoAppRendered
- if err := json.NewDecoder(bytes.NewReader(res.RenderedRaw)).Decode(&rendered); err != nil {
+ if err := json.NewDecoder(bytes.NewReader(ret.RenderedRaw)).Decode(&rendered); err != nil {
return "", nil
}
fmt.Println("111")
@@ -926,20 +1039,23 @@
},
soft.WithCommitToBranch("dodo"),
soft.WithForce(),
- )
+ ); err != nil {
+ return installer.ReleaseResources{}, err
+ }
+ return ret, nil
}
-func (s *DodoAppServer) initRepo(repo soft.RepoIO, appType string, network installer.Network, subdomain string) error {
+func (s *DodoAppServer) initRepo(repo soft.RepoIO, appType string, network installer.Network, subdomain string) (string, error) {
appType = strings.Replace(appType, ":", "-", 1)
appTmpl, err := s.appTmpls.Find(appType)
if err != nil {
- return err
+ return "", err
}
return repo.Do(func(fs soft.RepoFS) (string, error) {
if err := appTmpl.Render(network, subdomain, repo); err != nil {
return "", err
}
- return "init", nil
+ return initCommitMsg, nil
})
}
@@ -994,6 +1110,30 @@
}
}
+func (s *DodoAppServer) createCommit(name, hash, message string, err error, resources installer.ReleaseResources) error {
+ if err != nil {
+ fmt.Printf("Error: %s\n", err.Error())
+ if err := s.st.CreateCommit(name, hash, message, "FAILED", err.Error(), nil); err != nil {
+ fmt.Printf("Error: %s\n", err.Error())
+ return err
+ }
+ return err
+ }
+ var resB bytes.Buffer
+ if err := json.NewEncoder(&resB).Encode(resources); err != nil {
+ if err := s.st.CreateCommit(name, hash, message, "FAILED", err.Error(), nil); err != nil {
+ fmt.Printf("Error: %s\n", err.Error())
+ return err
+ }
+ return err
+ }
+ if err := s.st.CreateCommit(name, hash, message, "OK", "", resB.Bytes()); err != nil {
+ fmt.Printf("Error: %s\n", err.Error())
+ return err
+ }
+ return nil
+}
+
func pickNetwork(networks []installer.Network, network string) []installer.Network {
for _, n := range networks {
if n.Name == network {
@@ -1194,3 +1334,48 @@
}
}
}
+
+func extractResourceData(resources []installer.Resource) (resourceData, error) {
+ var ret resourceData
+ for _, r := range resources {
+ t, ok := r.Annotations["dodo.cloud/resource-type"]
+ if !ok {
+ continue
+ }
+ switch t {
+ case "volume":
+ name, ok := r.Annotations["dodo.cloud/resource.volume.name"]
+ if !ok {
+ return resourceData{}, fmt.Errorf("no name")
+ }
+ size, ok := r.Annotations["dodo.cloud/resource.volume.size"]
+ if !ok {
+ return resourceData{}, fmt.Errorf("no size")
+ }
+ ret.Volume = append(ret.Volume, volume{name, size})
+ case "postgresql":
+ name, ok := r.Annotations["dodo.cloud/resource.postgresql.name"]
+ if !ok {
+ return resourceData{}, fmt.Errorf("no name")
+ }
+ version, ok := r.Annotations["dodo.cloud/resource.postgresql.version"]
+ if !ok {
+ return resourceData{}, fmt.Errorf("no version")
+ }
+ volume, ok := r.Annotations["dodo.cloud/resource.postgresql.volume"]
+ if !ok {
+ return resourceData{}, fmt.Errorf("no volume")
+ }
+ ret.PostgreSQL = append(ret.PostgreSQL, postgresql{name, version, volume})
+ case "ingress":
+ host, ok := r.Annotations["dodo.cloud/resource.ingress.host"]
+ if !ok {
+ return resourceData{}, fmt.Errorf("no host")
+ }
+ ret.Ingress = append(ret.Ingress, ingress{host})
+ default:
+ fmt.Printf("Unknown resource: %+v\n", r.Annotations)
+ }
+ }
+ return ret, nil
+}