forked from toolshed/coop-cloud-backend
272 lines
6.5 KiB
Go
272 lines
6.5 KiB
Go
package cli
|
|
import (
|
|
"log"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"os/exec"
|
|
"encoding/json"
|
|
"strings"
|
|
"sort"
|
|
"github.com/samber/lo"
|
|
"sync"
|
|
"time"
|
|
|
|
appPkg "coopcloud.tech/abra/pkg/app"
|
|
client "coopcloud.tech/abra/pkg/client"
|
|
"coopcloud.tech/abra/pkg/config"
|
|
"coopcloud.tech/tagcmp"
|
|
|
|
)
|
|
|
|
func (h *abraHandler) handleListApps(w http.ResponseWriter, r *http.Request) {
|
|
cmd := exec.Command("abra", "app", "ls", "-m")
|
|
output, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
return
|
|
}
|
|
var resp ServerAppsResponse
|
|
// no filtering
|
|
listAppServer := ""
|
|
recipeFilter := ""
|
|
|
|
if err := json.Unmarshal(output, &resp); err != nil {
|
|
InternalServerError(w, errors.New(fmt.Sprintf("invalid JSON from CLI")))
|
|
return
|
|
}
|
|
|
|
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
apps, err := appPkg.GetApps(appFiles, recipeFilter)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
statuses := make(map[string]map[string]string)
|
|
// servers that are unreachable..
|
|
reachable := make(map[string]bool)
|
|
servers, err := config.ReadServerNames()
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
}
|
|
start := time.Now()
|
|
|
|
reachableCh := make(chan string)
|
|
//errCh := make(chan error)
|
|
var wg sync.WaitGroup
|
|
wg.Add(len(servers))
|
|
|
|
for _, server := range servers {
|
|
// check if its unreachable..
|
|
// this connection is retried, so takes 10 seconds per connection
|
|
// TODO: figure out a way to make this faster
|
|
// this is retried every server and connection so it really should be quicker
|
|
go func(server string) {
|
|
defer wg.Done()
|
|
log.Printf("working on: %s", server)
|
|
timeout := client.WithTimeout(5)
|
|
if _, err := client.New(server, timeout); err != nil {
|
|
//errCh <- err
|
|
|
|
OnError(errors.New(fmt.Sprintf("connection failed or timed out at %s: %s", server, err)))
|
|
reachable[server] = false
|
|
} else {
|
|
reachableCh <- server
|
|
}
|
|
}(server)
|
|
}
|
|
go func() {
|
|
for server := range reachableCh {
|
|
log.Printf("%s is reachable", server)
|
|
reachable[server] = true
|
|
}
|
|
}()
|
|
wg.Wait()
|
|
|
|
elapsed := time.Since(start)
|
|
log.Printf("Checking server availability took %s", elapsed)
|
|
// TODO: I think this will check every server for every app, need to remove some first.
|
|
// rapps is all reachable apps
|
|
rapps := lo.Filter(apps, func(app appPkg.App, index int) bool {
|
|
return reachable[app.Server]
|
|
})
|
|
statuses, err = appPkg.GetAppStatuses(rapps, true)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
// TODO: if a server is unreachable, skip its apps, maybe flag something?
|
|
for _, app := range apps {
|
|
var stats ServerApps
|
|
|
|
stats = resp[app.Server]
|
|
var appStats *AbraApp
|
|
for _, sapp := range stats.Apps {
|
|
if sapp.AppName == app.Name {
|
|
appStats = &sapp
|
|
}
|
|
}
|
|
|
|
if !reachable[app.Server] {
|
|
appStats.Chaos = "unknown"
|
|
appStats.ChaosVersion = "unknown"
|
|
appStats.Version = "unknown"
|
|
appStats.Status = "Server unreachable"
|
|
for i, sapp := range stats.Apps {
|
|
if sapp.AppName == app.Name {
|
|
stats.Apps[i] = *appStats
|
|
}
|
|
}
|
|
resp[app.Server] = stats
|
|
continue
|
|
}
|
|
|
|
status := "unknown"
|
|
version := "unknown"
|
|
chaos := "unknown"
|
|
chaosVersion := "unknown"
|
|
if statusMeta, ok := statuses[app.StackName()]; ok {
|
|
if currentVersion, exists := statusMeta["version"]; exists {
|
|
if currentVersion != "" {
|
|
version = currentVersion
|
|
}
|
|
}
|
|
if chaosDeploy, exists := statusMeta["chaos"]; exists {
|
|
chaos = chaosDeploy
|
|
}
|
|
if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
|
|
chaosVersion = chaosDeployVersion
|
|
}
|
|
if statusMeta["status"] != "" {
|
|
status = statusMeta["status"]
|
|
}
|
|
stats.VersionCount++
|
|
} else {
|
|
stats.UnversionedCount++
|
|
}
|
|
|
|
appStats.Status = status
|
|
appStats.Chaos = chaos
|
|
appStats.ChaosVersion = chaosVersion
|
|
appStats.Version = version
|
|
localApp := true
|
|
|
|
var newUpdates []string
|
|
if version != "unknown" && chaos == "false" {
|
|
if err := app.Recipe.EnsureExists(); err != nil {
|
|
InternalServerError(w, errors.New(fmt.Sprintf("unable to clone %s: %s", app.Name, err)))
|
|
return
|
|
}
|
|
|
|
updates, err := app.Recipe.Tags()
|
|
if err != nil {
|
|
localApp = false
|
|
} else {
|
|
parsedVersion, err := tagcmp.Parse(version)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
for _, update := range updates {
|
|
if ok := tagcmp.IsParsable(update); !ok {
|
|
log.Printf("unable to parse %s, skipping as upgrade option", update)
|
|
continue
|
|
}
|
|
|
|
parsedUpdate, err := tagcmp.Parse(update)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
if update != version && parsedUpdate.IsGreaterThan(parsedVersion) {
|
|
newUpdates = append(newUpdates, update)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if len(newUpdates) == 0 {
|
|
if version == "unknown" {
|
|
appStats.Upgrade = "unknown"
|
|
} else if localApp {
|
|
appStats.Upgrade = "latest"
|
|
stats.LatestCount++
|
|
} else {
|
|
appStats.Upgrade = "unknown"
|
|
}
|
|
} else {
|
|
newUpdates = SortVersionsDesc(newUpdates)
|
|
appStats.Upgrade = strings.Join(newUpdates, "\n")
|
|
stats.UpgradeCount++
|
|
}
|
|
|
|
for i, sapp := range stats.Apps {
|
|
if sapp.AppName == app.Name {
|
|
stats.Apps[i] = *appStats
|
|
}
|
|
}
|
|
resp[app.Server] = stats
|
|
}
|
|
jsonBytes, err := json.Marshal(resp)
|
|
if err != nil {
|
|
InternalServerError(w, errors.New(fmt.Sprintf("JSON conversion failed: %s\n", err)))
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(jsonBytes)
|
|
}
|
|
func (h *abraHandler) handleListServers (w http.ResponseWriter, r *http.Request) {
|
|
cmd := exec.Command("abra", "server", "ls", "-m")
|
|
abraServers, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
return
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(abraServers)
|
|
}
|
|
|
|
func (h *abraHandler) handleListVersions (w http.ResponseWriter, r *http.Request, appName string) {
|
|
app, err := appPkg.Get(appName)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
return
|
|
}
|
|
versions, err := app.Recipe.Tags()
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
jsonBytes, err := json.Marshal(versions)
|
|
if err != nil {
|
|
InternalServerError(w, err)
|
|
return
|
|
}
|
|
|
|
log.Printf("%v", versions)
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.WriteHeader(http.StatusOK)
|
|
w.Write(jsonBytes)
|
|
}
|
|
func SortVersionsDesc(versions []string) []string {
|
|
var tags []tagcmp.Tag
|
|
|
|
for _, v := range versions {
|
|
parsed, _ := tagcmp.Parse(v) // skips unsupported tags
|
|
tags = append(tags, parsed)
|
|
}
|
|
|
|
sort.Sort(tagcmp.ByTagDesc(tags))
|
|
|
|
var desc []string
|
|
for _, t := range tags {
|
|
desc = append(desc, t.String())
|
|
}
|
|
|
|
return desc
|
|
}
|