DodoApp: Status page
Implements basic status page, listing all apps and their commit
statuses. Separates web and api endpoints. Unifies API addresses a bit.
Change-Id: I98f9f949a49b60e80e188f7b51ec0e967666e65b
diff --git a/core/installer/welcome/dodo_app.go b/core/installer/welcome/dodo_app.go
index 8cfa72f..89b7ad2 100644
--- a/core/installer/welcome/dodo_app.go
+++ b/core/installer/welcome/dodo_app.go
@@ -9,7 +9,6 @@
"net/http"
"strings"
"sync"
- "time"
"github.com/giolekva/pcloud/core/installer"
"github.com/giolekva/pcloud/core/installer/soft"
@@ -18,13 +17,15 @@
)
const (
- configRepoName = "config"
+ ConfigRepoName = "config"
namespacesFile = "/namespaces.json"
)
type DodoAppServer struct {
l sync.Locker
+ st Store
port int
+ apiPort int
self string
sshKey string
gitRepoPublicKey string
@@ -39,7 +40,9 @@
// TODO(gio): Initialize appNs on startup
func NewDodoAppServer(
+ st Store,
port int,
+ apiPort int,
self string,
sshKey string,
gitRepoPublicKey string,
@@ -49,16 +52,11 @@
jc installer.JobCreator,
env installer.EnvConfig,
) (*DodoAppServer, error) {
- if ok, err := client.RepoExists(configRepoName); err != nil {
- return nil, err
- } else if !ok {
- if err := client.AddRepository(configRepoName); err != nil {
- return nil, err
- }
- }
s := &DodoAppServer{
&sync.Mutex{},
+ st,
port,
+ apiPort,
self,
sshKey,
gitRepoPublicKey,
@@ -70,7 +68,7 @@
map[string]map[string]struct{}{},
map[string]string{},
}
- config, err := client.GetRepo(configRepoName)
+ config, err := client.GetRepo(ConfigRepoName)
if err != nil {
return nil, err
}
@@ -87,12 +85,50 @@
}
func (s *DodoAppServer) Start() error {
- r := mux.NewRouter()
- r.HandleFunc("/update", s.handleUpdate)
- r.HandleFunc("/register-worker", s.handleRegisterWorker).Methods(http.MethodPost)
- r.HandleFunc("/api/apps", s.handleCreateApp).Methods(http.MethodPost)
- r.HandleFunc("/api/add-admin-key", s.handleAddAdminKey).Methods(http.MethodPost)
- return http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
+ e := make(chan error)
+ go func() {
+ r := mux.NewRouter()
+ r.HandleFunc("/status/{app-name}", s.handleAppStatus).Methods(http.MethodGet)
+ r.HandleFunc("/status", s.handleStatus).Methods(http.MethodGet)
+ e <- http.ListenAndServe(fmt.Sprintf(":%d", s.port), r)
+ }()
+ go func() {
+ r := mux.NewRouter()
+ r.HandleFunc("/update", s.handleUpdate)
+ r.HandleFunc("/api/apps/{app-name}/workers", s.handleRegisterWorker).Methods(http.MethodPost)
+ r.HandleFunc("/api/apps", s.handleCreateApp).Methods(http.MethodPost)
+ r.HandleFunc("/api/add-admin-key", s.handleAddAdminKey).Methods(http.MethodPost)
+ e <- http.ListenAndServe(fmt.Sprintf(":%d", s.apiPort), r)
+ }()
+ return <-e
+}
+
+func (s *DodoAppServer) handleStatus(w http.ResponseWriter, r *http.Request) {
+ apps, err := s.st.GetApps()
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ for _, a := range apps {
+ fmt.Fprintf(w, "%s\n", a)
+ }
+}
+
+func (s *DodoAppServer) handleAppStatus(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
+ }
+ commits, err := s.st.GetCommitHistory(appName)
+ if err != nil {
+ http.Error(w, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ for _, c := range commits {
+ fmt.Fprintf(w, "%s %s\n", c.Hash, c.Message)
+ }
}
type updateReq struct {
@@ -100,6 +136,7 @@
Repository struct {
Name string `json:"name"`
} `json:"repository"`
+ After string `json:"after"`
}
func (s *DodoAppServer) handleUpdate(w http.ResponseWriter, r *http.Request) {
@@ -113,38 +150,49 @@
fmt.Println(err)
return
}
- if req.Ref != "refs/heads/master" || strings.HasPrefix(req.Repository.Name, configRepoName) {
+ if req.Ref != "refs/heads/master" || req.Repository.Name == ConfigRepoName {
return
}
+ // TODO(gio): Create commit record on app init as well
go func() {
- time.Sleep(20 * time.Second)
if err := s.updateDodoApp(req.Repository.Name, s.appNs[req.Repository.Name]); err != nil {
- fmt.Println(err)
+ if err := s.st.CreateCommit(req.Repository.Name, req.After, err.Error()); err != nil {
+ fmt.Printf("Error: %s\n", err.Error())
+ return
+ }
+ }
+ if err := s.st.CreateCommit(req.Repository.Name, req.After, "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
+ http.Get(fmt.Sprintf("http://%s/update", addr))
+ }()
}
}()
- for addr, _ := range s.workers[req.Repository.Name] {
- go func() {
- // TODO(gio): make port configurable
- http.Get(fmt.Sprintf("http://%s:3000/update", addr))
- }()
- }
}
type registerWorkerReq struct {
- AppId string `json:"appId"`
Address string `json:"address"`
}
func (s *DodoAppServer) handleRegisterWorker(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
+ }
var req registerWorkerReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
- if _, ok := s.workers[req.AppId]; !ok {
- s.workers[req.AppId] = map[string]struct{}{}
+ if _, ok := s.workers[appName]; !ok {
+ s.workers[appName] = map[string]struct{}{}
}
- s.workers[req.AppId][req.Address] = struct{}{}
+ s.workers[appName][req.Address] = struct{}{}
}
type createAppReq struct {
@@ -187,6 +235,9 @@
} else if ok {
return nil
}
+ if err := s.st.CreateApp(appName); err != nil {
+ return err
+ }
if err := s.client.AddRepository(appName); err != nil {
return err
}
@@ -212,7 +263,7 @@
if err := s.updateDodoApp(appName, namespace); err != nil {
return err
}
- repo, err := s.client.GetRepo(configRepoName)
+ repo, err := s.client.GetRepo(ConfigRepoName)
if err != nil {
return err
}
@@ -337,10 +388,10 @@
"/.dodo/app",
namespace,
map[string]any{
- "repoAddr": repo.FullAddress(),
- "registerWorkerAddr": fmt.Sprintf("http://%s/register-worker", s.self),
- "appId": name,
- "sshPrivateKey": s.sshKey,
+ "repoAddr": repo.FullAddress(),
+ "managerAddr": fmt.Sprintf("http://%s", s.self),
+ "appId": name,
+ "sshPrivateKey": s.sshKey,
},
installer.WithConfig(&s.env),
installer.WithLocalChartGenerator(lg),
diff --git a/core/installer/welcome/store.go b/core/installer/welcome/store.go
new file mode 100644
index 0000000..0ae5f4e
--- /dev/null
+++ b/core/installer/welcome/store.go
@@ -0,0 +1,103 @@
+package welcome
+
+import (
+ "database/sql"
+
+ "github.com/giolekva/pcloud/core/installer/soft"
+)
+
+type Commit struct {
+ Hash string
+ Message string
+}
+
+type Store interface {
+ GetApps() ([]string, error)
+ CreateApp(name string) error
+ CreateCommit(name, hash, message string) error
+ GetCommitHistory(name string) ([]Commit, error)
+}
+
+func NewStore(cf soft.RepoIO, db *sql.DB) (Store, error) {
+ s := &storeImpl{cf, db}
+ if err := s.init(); err != nil {
+ return nil, err
+ }
+ return s, nil
+}
+
+type storeImpl struct {
+ cf soft.RepoIO
+ db *sql.DB
+}
+
+func (s *storeImpl) init() error {
+ _, err := s.db.Exec(`
+ CREATE TABLE IF NOT EXISTS apps (
+ name TEXT PRIMARY KEY
+ );
+ CREATE TABLE IF NOT EXISTS commits (
+ app_name TEXT,
+ hash TEXT,
+ message TEXT
+ );
+ `)
+ return err
+
+}
+
+func (s *storeImpl) CreateApp(name string) error {
+ query := `INSERT INTO apps (name) VALUES (?)`
+ _, err := s.db.Exec(query, name)
+ return err
+}
+
+func (s *storeImpl) GetApps() ([]string, error) {
+ query := `SELECT name FROM apps`
+ rows, err := s.db.Query(query)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ ret := []string{}
+ for rows.Next() {
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ var name string
+ if err := rows.Scan(&name); err != nil {
+ return nil, err
+ }
+ ret = append(ret, name)
+
+ }
+ return ret, nil
+}
+
+func (s *storeImpl) CreateCommit(name, hash, message string) error {
+ query := `INSERT INTO commits (app_name, hash, message) VALUES (?, ?, ?)`
+ _, err := s.db.Exec(query, name, hash, message)
+ return err
+}
+
+func (s *storeImpl) GetCommitHistory(name string) ([]Commit, error) {
+ query := `SELECT hash, message FROM commits WHERE app_name = ?`
+ rows, err := s.db.Query(query, name)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+ ret := []Commit{}
+ for rows.Next() {
+ if err := rows.Err(); err != nil {
+ return nil, err
+ }
+ var c Commit
+ if err := rows.Scan(&c.Hash, &c.Message); err != nil {
+ return nil, err
+ }
+ ret = append(ret, c)
+
+ }
+ return ret, nil
+}