Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 916c3036de | |||
| 3d658103b6 | |||
| 619b107a49 | |||
| 4a8d4b281c | |||
| aa1bd46039 | |||
| 491d9ffc10 | |||
| d46144c4f2 |
+1
-4
@@ -1,6 +1,6 @@
|
||||
FROM golang:1.25
|
||||
RUN mkdir /backend
|
||||
COPY --parents ["api", "cli", "internal", "go.mod", "go.sum", "app.go", "/backend/"]
|
||||
COPY --parents ["cli", "internal", "go.mod", "go.sum", "app.go", "/backend/"]
|
||||
RUN go build -C /backend -o gobackend
|
||||
|
||||
FROM node
|
||||
@@ -9,9 +9,6 @@ COPY --from=0 /backend/gobackend /home/node/wizard/gobackend
|
||||
|
||||
USER node
|
||||
COPY ./command.sh /
|
||||
COPY ssh_config /home/node/.ssh/config
|
||||
COPY id_ed25519_* /home/node/.ssh/
|
||||
COPY dot_abra /home/node/.abra/
|
||||
RUN curl https://install.abra.coopcloud.tech | bash
|
||||
ENV ABRA_BIN=/home/node/.local/bin/abra
|
||||
# RUN $ABRA_BIN recipe fetch -a
|
||||
|
||||
@@ -5,12 +5,12 @@ Integrates with https://git.coopcloud.tech/toolshed/coop-cloud-front.
|
||||
## Starting the service with Docker
|
||||
Build the container:
|
||||
```bash
|
||||
docker build -t abra-wizard:latest
|
||||
docker build -t coop-cloud-wizard:latest .
|
||||
```
|
||||
|
||||
Run the container:
|
||||
```bash
|
||||
docker run abra-wizard
|
||||
docker run -u 1000:1000 -p 5173:5173 -p 3000:3000 -v $HOME/.ssh:/home/node/.ssh -v $HOME/.abra:/home/node/.abra coop-cloud-wizard
|
||||
```
|
||||
|
||||
## Getting started with development
|
||||
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"coop-cloud-backend/internal"
|
||||
)
|
||||
// Upgrader is used to upgrade HTTP connections to WebSocket connections.
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Upgrade the HTTP connection to a WebSocket connection
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error upgrading:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
// Listen for incoming messages
|
||||
for {
|
||||
// Read message from the client
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println("Error reading message:", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("Received: %s\\n", message)
|
||||
// Echo the message back to the client
|
||||
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
fmt.Println("Error writing message:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type abraHandler struct{
|
||||
mux *http.ServeMux
|
||||
}
|
||||
func newAbraHandler() *abraHandler {
|
||||
h := &abraHandler{
|
||||
mux: http.NewServeMux(),
|
||||
}
|
||||
h.mux.HandleFunc("/api/abra/apps", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleListApps(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/abra/apps/{appId}/deploy", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
if r.Header.Get("Chaos") == "true"{
|
||||
internal.Chaos = true
|
||||
} else {
|
||||
internal.Chaos = false
|
||||
}
|
||||
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
h.handleDeployApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/abra/apps/{appId}/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
h.handleUndeployApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/abra/servers", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleListServers(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/abra/catalogue", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
|
||||
if r.Method == http.MethodOptions {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleListCatalogue(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
return h
|
||||
}
|
||||
func StartAPI() {
|
||||
http.HandleFunc("/ws", wsHandler)
|
||||
fmt.Println("WebSocket server started on :3001")
|
||||
err := http.ListenAndServe(":3001", nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error starting server:", err)
|
||||
}
|
||||
|
||||
h := newAbraHandler()
|
||||
fmt.Println("Server started on port 3000")
|
||||
http.ListenAndServe(":3000", h)
|
||||
}
|
||||
|
||||
func (h *abraHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Pre-processing: logging
|
||||
log.Printf("Incoming %s request: %s\n", r.Method, r.URL.Path)
|
||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
|
||||
|
||||
// Delegate to internal mux
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"encoding/json"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
|
||||
"coop-cloud-backend/internal"
|
||||
)
|
||||
|
||||
func (h *abraHandler) handleListCatalogue(w http.ResponseWriter, r *http.Request) {
|
||||
catalogue, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
recipes := catalogue.Flatten()
|
||||
|
||||
jsonBytes, err := json.Marshal(recipes)
|
||||
if err != nil {
|
||||
log.Printf("JSON conversion failed: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("JSON conversion failed: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
-299
@@ -1,299 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"encoding/json"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
|
||||
"coopcloud.tech/abra/pkg/ui"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/deploy"
|
||||
|
||||
"net/http"
|
||||
"github.com/gorilla/websocket"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"coop-cloud-backend/internal"
|
||||
)
|
||||
func (h *abraHandler) handleDeployApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("App Id: %s", appName)
|
||||
app, err := GetApp(appName)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Printf("Error Connecting to Docker client: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error Connecting to Docker client: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
||||
if err != nil {
|
||||
log.Printf("Error checking deploy status: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error checking deploy status: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if deployMeta.IsDeployed {
|
||||
log.Printf("App already deployed\n")
|
||||
http.Error(w, "App already deployed\n", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// logic differs from CLI, we only want to take either
|
||||
// 1. the chaos version
|
||||
// 2. TODO: the version in the .env file
|
||||
// 3. the latest version
|
||||
// we never take: specific CLI verison (maybe will support this) or the deployed version
|
||||
internal.Chaos = false
|
||||
toDeployVersion, err := getDeployVersion(deployMeta, app)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := validateSecrets(cl, app); err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
stackName := app.StackName()
|
||||
deployOpts := stack.Deploy{
|
||||
Composefiles: composeFiles,
|
||||
Namespace: stackName,
|
||||
Prune: false,
|
||||
ResolveImage: stack.ResolveImageAlways,
|
||||
Detach: false,
|
||||
}
|
||||
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
||||
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
||||
if internal.Chaos {
|
||||
appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion)
|
||||
}
|
||||
|
||||
versionLabel := toDeployVersion
|
||||
if internal.Chaos {
|
||||
for _, service := range compose.Services {
|
||||
if service.Name == "app" {
|
||||
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
|
||||
// NOTE(d1): keep non-chaos version labbeling when doing chaos ops
|
||||
versionLabel = service.Deploy.Labels[labelKey]
|
||||
}
|
||||
}
|
||||
}
|
||||
appPkg.SetVersionLabel(compose, stackName, versionLabel)
|
||||
|
||||
newRecipeWithDeployVersion := fmt.Sprintf("%s:%s", app.Recipe.Name, toDeployVersion)
|
||||
appPkg.ExposeAllEnv(stackName, compose, app.Env, newRecipeWithDeployVersion)
|
||||
|
||||
envVars, err := appPkg.CheckEnv(app)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// doesn't really get used at all right now
|
||||
deployWarnMessages := []string{}
|
||||
for _, envVar := range envVars {
|
||||
if !envVar.Present {
|
||||
deployWarnMessages = append(deployWarnMessages,
|
||||
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//skipping domain checks like crazy
|
||||
//commented code is to show deploy overview before deploy
|
||||
|
||||
/*
|
||||
deployedVersion := config.MISSING_DEFAULT
|
||||
if deployMeta.IsDeployed {
|
||||
deployedVersion = deployMeta.Version
|
||||
if deployMeta.IsChaos {
|
||||
deployedVersion = deployMeta.ChaosVersion
|
||||
}
|
||||
}
|
||||
ShowUnchanged := false
|
||||
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, ShowUnchanged)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Gather configs
|
||||
configInfo, err := deploy.GatherConfigsForDeploy(cl, app, compose, app.Env, ShowUnchanged)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Gather images
|
||||
imageInfo, err := deploy.GatherImagesForDeploy(cl, app, compose, ShowUnchanged)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}*/
|
||||
|
||||
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
f, err := app.Filters(true, false, serviceNames...)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// in future allow user input?
|
||||
DontWaitConverge := true
|
||||
NoInput := true
|
||||
if err := stack.RunDeploy(
|
||||
cl,
|
||||
deployOpts,
|
||||
compose,
|
||||
app.Name,
|
||||
app.Server,
|
||||
DontWaitConverge,
|
||||
NoInput,
|
||||
f,
|
||||
); err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
// Implement my own WaitOnServices in order to wrap ui.Model in order to emit JSON updates
|
||||
// over Websocket connection
|
||||
var serviceIDs []ui.ServiceMeta
|
||||
namespace := convert.NewNamespace(deployOpts.Namespace)
|
||||
|
||||
existingServices, err := stack.GetStackServices(context.Background(), cl, namespace.Name())
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
for _, service := range existingServices {
|
||||
serviceIDs = append(serviceIDs, ui.ServiceMeta{
|
||||
Name: service.Spec.Name,
|
||||
ID: service.ID,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
waitOpts := WaitOpts{
|
||||
Services: serviceIDs,
|
||||
AppName: app.Name,
|
||||
ServerName: app.Server,
|
||||
NoInput: NoInput,
|
||||
Filters: f,
|
||||
}
|
||||
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
type StateEmitter interface {
|
||||
Emit(DeployState)
|
||||
}
|
||||
|
||||
type WebsocketEmitter struct {
|
||||
conn *websocket.Conn
|
||||
}
|
||||
|
||||
func (w *WebsocketEmitter) Emit(state DeployState) {
|
||||
b, _ := json.Marshal(state)
|
||||
w.conn.WriteMessage(websocket.TextMessage, b)
|
||||
}
|
||||
|
||||
type WrappedModel struct {
|
||||
inner tea.Model
|
||||
emitter StateEmitter
|
||||
}
|
||||
|
||||
func (m WrappedModel) Init() tea.Cmd {
|
||||
return m.inner.Init()
|
||||
}
|
||||
|
||||
func (m WrappedModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
innerState, cmds := m.inner.Update(msg)
|
||||
|
||||
m.inner = innerState
|
||||
|
||||
m.emitter.Emit(s.State())
|
||||
|
||||
return m, cmds
|
||||
}
|
||||
|
||||
func (m WrappedModel) View() string {
|
||||
return m.inner.View()
|
||||
}
|
||||
|
||||
func (m *ui.Model) State() DeployState {
|
||||
var streams []DeployStream
|
||||
if m.Streams != nil {
|
||||
for _, s := range *m.Streams {
|
||||
streams = append(streams, DeployStream{
|
||||
Name: s.Name,
|
||||
id: s.id,
|
||||
status: s.status,
|
||||
retries: s.retries,
|
||||
health: s.health,
|
||||
rollback: s.rollback,
|
||||
})
|
||||
}
|
||||
}
|
||||
var logs []string
|
||||
if m.Logs != nil {
|
||||
logs = *m.Logs
|
||||
}
|
||||
return DeployState{
|
||||
AppName: m.appName,
|
||||
Streams: streams,
|
||||
Logs: logs,
|
||||
Failed: m.Failed,
|
||||
TimedOut: m.TimedOut,
|
||||
Quit: m.Quit,
|
||||
Count: m.count,
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"net/http"
|
||||
)
|
||||
func InternalServerErrorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 Internal Server Error"))
|
||||
}
|
||||
|
||||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("404 Not Found"))
|
||||
}
|
||||
-153
@@ -1,153 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"encoding/json"
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coop-cloud-backend/internal"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h *abraHandler) handleListApps(w http.ResponseWriter, r *http.Request) {
|
||||
appNames, err := GetAppNames()
|
||||
if err != nil {
|
||||
log.Printf("Error getting app names: %s\n", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
// counts the number of apps in a server to initialize slices later
|
||||
serverAppCount := make(map[string]int)
|
||||
|
||||
remoteApps := make([]appPkg.App, 0, len(appNames))
|
||||
for _, appName := range appNames {
|
||||
remoteApp, err := GetApp(appName)
|
||||
serverAppCount[remoteApp.Server] += 1
|
||||
if err != nil {
|
||||
log.Printf("Error getting app %s: %s\n", appName, err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
remoteApps = append(remoteApps, remoteApp)
|
||||
}
|
||||
|
||||
appStatuses, err := GetAppStatuses(remoteApps)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
abraAppResponse := ServerAppsResponse{}
|
||||
for _, app := range remoteApps {
|
||||
serverApps, ok := abraAppResponse[app.Server]
|
||||
if !ok { // create the slice
|
||||
// ever other field initializes to 0
|
||||
serverApps = ServerApps{
|
||||
Apps: make([]AbraApp, 0, serverAppCount[app.Server]),
|
||||
}
|
||||
}
|
||||
appInfo, ok := appStatuses[app.StackName()]
|
||||
if ok {
|
||||
log.Printf("app %s is deployed\n", app.Name)
|
||||
serverApps.Apps = append(serverApps.Apps, appTranspose(app, appInfo))
|
||||
serverApps.AppCount += 1
|
||||
// assume these are true rn idk
|
||||
serverApps.VersionCount += 1
|
||||
serverApps.LatestCount += 1
|
||||
} else {
|
||||
log.Printf("app %s is undeployed\n", app.Name)
|
||||
appT, err := appTransposeUndeployed(app)
|
||||
if err != nil {
|
||||
log.Printf("app transpose failed: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("app transpose failed: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
serverApps.Apps = append(serverApps.Apps, appT)
|
||||
serverApps.AppCount += 1
|
||||
// assume these are true rn idk
|
||||
serverApps.VersionCount += 1
|
||||
serverApps.LatestCount += 1
|
||||
}
|
||||
abraAppResponse[app.Server] = serverApps
|
||||
}
|
||||
jsonBytes, err := json.Marshal(abraAppResponse)
|
||||
if err != nil {
|
||||
log.Printf("JSON conversion failed: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("JSON conversion failed: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
func appTransposeUndeployed(app appPkg.App) (AbraApp, error) {
|
||||
config, err := app.Recipe.GetComposeConfig(app.Env)
|
||||
if err != nil {
|
||||
return AbraApp{}, err
|
||||
}
|
||||
version := GetLabel(config, app.StackName(), "version")
|
||||
if version == "" {
|
||||
version = "unknown"
|
||||
}
|
||||
return AbraApp{
|
||||
AppName: app.Name,
|
||||
Server: app.Server,
|
||||
Recipe: app.Recipe.Name,
|
||||
Domain: app.Domain,
|
||||
Chaos: "false",
|
||||
Status: "undeployed",
|
||||
ChaosVersion: "unknown",
|
||||
Version: version,
|
||||
Upgrade: "latest",
|
||||
}, nil
|
||||
}
|
||||
func appTranspose(app appPkg.App, psInfo map[string]string) AbraApp {
|
||||
return AbraApp{
|
||||
AppName: app.Name,
|
||||
Server: app.Server,
|
||||
Recipe: app.Recipe.Name,
|
||||
Domain: app.Domain,
|
||||
Chaos: psInfo["chaos"],
|
||||
Status: psInfo["status"],
|
||||
ChaosVersion: getOrDefault(psInfo, "chaosVersion", "unknown"),
|
||||
Version: psInfo["version"],
|
||||
Upgrade: "latest",
|
||||
}
|
||||
}
|
||||
|
||||
func getOrDefault(m map[string]string, key, def string) string {
|
||||
if v, ok := m[key]; ok {
|
||||
return v
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
func (h *abraHandler) handleListServers (w http.ResponseWriter, r *http.Request) {
|
||||
servers, err := GetServers()
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
serverNames, err := ReadServerNames()
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
abraServers := make([]AbraServer, 0, len(servers))
|
||||
for i := range len(servers) {
|
||||
abraServers = append(abraServers,
|
||||
AbraServer{
|
||||
Name: serverNames[i],
|
||||
Host: servers[i],
|
||||
},
|
||||
)
|
||||
}
|
||||
jsonBytes, err := json.Marshal(abraServers)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package api
|
||||
|
||||
// Represents an App, follows the format of
|
||||
// https://git.coopcloud.tech/toolshed/coop-cloud-front
|
||||
type AbraApp struct {
|
||||
Server string `json:"server"`
|
||||
Recipe string `json:"recipe`
|
||||
AppName string `json:"appName"`
|
||||
Domain string `json:"domain"`
|
||||
Status string `json:"status"`
|
||||
Chaos string `json:"chaos"`
|
||||
ChaosVersion string `json:"chaosVersion"`
|
||||
Version string `json:"version"`
|
||||
Upgrade string `json:"upgrade"`
|
||||
}
|
||||
|
||||
type AbraServer struct {
|
||||
Name string `json:"name"`
|
||||
Host string `json:"host"`
|
||||
}
|
||||
|
||||
type ServerAppsResponse map[string]ServerApps
|
||||
|
||||
type ServerApps struct {
|
||||
Apps []AbraApp `json:"apps"`
|
||||
AppCount int `json:"appCount"`
|
||||
VersionCount int `json:"versionCount"`
|
||||
UnversionedCount int `json:"unversionedCount"`
|
||||
LatestCount int `json:"latestCount"`
|
||||
UpgradeCount int `json:"upgradeCount"`
|
||||
}
|
||||
|
||||
type DeployState struct {
|
||||
AppName string `json:"appName"`
|
||||
Streams []DeployStream `json:"streams"`
|
||||
Logs []string `json:"logs"`
|
||||
Failed bool `json:"failed"`
|
||||
TimedOut bool `json:"timedOut"`
|
||||
Quit bool `json:"quit"`
|
||||
Count int `json:"count"`
|
||||
}
|
||||
|
||||
type DeployStream struct {
|
||||
Name string `json:"Name"`
|
||||
id string `json:"id"`
|
||||
status string `json:"status"`
|
||||
retries int `json:"retries"`
|
||||
health string `json:"health"`
|
||||
rollback bool `json:"rollback"`
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"encoding/json"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
|
||||
"coop-cloud-backend/internal"
|
||||
)
|
||||
|
||||
func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
catalogue, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
recipes := catalogue.Flatten()
|
||||
|
||||
jsonBytes, err := json.Marshal(recipes)
|
||||
if err != nil {
|
||||
log.Printf("JSON conversion failed: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("JSON conversion failed: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
|
||||
"net/http"
|
||||
)
|
||||
func (h *abraHandler) handleUndeployApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("App Id: %s", appName)
|
||||
app, err := GetApp(appName)
|
||||
if err != nil {
|
||||
log.Printf("Error getting app %s: %s\n", appName, err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
// think this just checks to make sure we have the recipe for this app
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !deployMeta.IsDeployed {
|
||||
log.Fatal("%s is not deployed?", app.Name)
|
||||
InternalServerErrorHandler(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
version := deployMeta.Version
|
||||
if deployMeta.IsChaos {
|
||||
version = deployMeta.ChaosVersion
|
||||
}
|
||||
|
||||
rmOpts := stack.Remove{
|
||||
Namespaces: []string{app.StackName()},
|
||||
Detach: false,
|
||||
}
|
||||
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if err := app.WriteRecipeVersion(version, false); err != nil {
|
||||
log.Printf("writing recipe version failed: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("writing recipe version failed: %s\n", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
"fmt"
|
||||
"errors"
|
||||
"log"
|
||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
formatter "coopcloud.tech/abra/pkg/formatter"
|
||||
secret "coopcloud.tech/abra/pkg/secret"
|
||||
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
|
||||
"coop-cloud-backend/internal"
|
||||
)
|
||||
func getDeployVersion(deployMeta stack.DeployMeta, app appPkg.App) (string, error) {
|
||||
if internal.Chaos {
|
||||
v, err := app.Recipe.ChaosVersion()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.Printf("version: taking chaos version: %s", v)
|
||||
return v, nil
|
||||
}
|
||||
|
||||
v, err := getLatestVersionOrCommit(app)
|
||||
log.Printf("version: taking new recipe version: %s", v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return v, nil
|
||||
}
|
||||
|
||||
|
||||
func getLatestVersionOrCommit(app appPkg.App) (string, error) {
|
||||
recipeVersions, warnings, err := app.Recipe.GetRecipeVersions()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, warning := range warnings {
|
||||
log.Printf(warning)
|
||||
}
|
||||
|
||||
if len(recipeVersions) > 0 && !internal.Chaos {
|
||||
latest := recipeVersions[len(recipeVersions)-1]
|
||||
for tag := range latest {
|
||||
log.Printf("selected latest recipe version: %s (from %d available versions)", tag, len(recipeVersions))
|
||||
return tag, nil
|
||||
}
|
||||
}
|
||||
|
||||
head, err := app.Recipe.Head()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return formatter.SmallSHA(head.String()), nil
|
||||
}
|
||||
|
||||
func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
|
||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
secStats, err := secret.PollSecretsStatus(cl, app)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, secStat := range secStats {
|
||||
if !secStat.CreatedOnRemote {
|
||||
secretConfig := secretsConfig[secStat.LocalName]
|
||||
if secretConfig.SkipGenerate {
|
||||
return errors.New(fmt.Sprintf("secret not inserted (#generate=false): %s", secStat.LocalName))
|
||||
}
|
||||
return errors.New(fmt.Sprintf("secret not generated: %s", secStat.LocalName))
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package api
|
||||
import (
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
|
||||
composetypes "github.com/docker/cli/cli/compose/types"
|
||||
)
|
||||
func GetLabel(compose *composetypes.Config, stackName string, label string) string {
|
||||
return appPkg.GetLabel(compose, stackName, label)
|
||||
}
|
||||
|
||||
func GetAppStatuses(apps []appPkg.App) (map[string]map[string]string, error) {
|
||||
return appPkg.GetAppStatuses(apps, true)
|
||||
}
|
||||
func GetApp(appName string) (appPkg.App, error) {
|
||||
return appPkg.Get(appName)
|
||||
}
|
||||
|
||||
func GetAppNames() ([]string, error) {
|
||||
return appPkg.GetAppNames()
|
||||
}
|
||||
|
||||
func GetAppServiceNames(appName string) ([]string, error) {
|
||||
return appPkg.GetAppServiceNames(appName)
|
||||
}
|
||||
|
||||
func GetServers() ([]string, error) {
|
||||
return config.GetServers()
|
||||
}
|
||||
|
||||
func ReadServerNames() ([]string, error) {
|
||||
return config.ReadServerNames()
|
||||
}
|
||||
+67
-39
@@ -2,44 +2,55 @@ package cli
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"net/http"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"coop-cloud-backend/internal"
|
||||
)
|
||||
// Upgrader is used to upgrade HTTP connections to WebSocket connections.
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
// // Upgrader is used to upgrade HTTP connections to WebSocket connections.
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// CheckOrigin: func(r *http.Request) bool {
|
||||
// return true
|
||||
// },
|
||||
// }
|
||||
// func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// // Upgrade the HTTP connection to a WebSocket connection
|
||||
// conn, err := upgrader.Upgrade(w, r, nil)
|
||||
// if err != nil {
|
||||
// fmt.Println("Error upgrading:", err)
|
||||
// return
|
||||
// }
|
||||
// defer conn.Close()
|
||||
// // Listen for incoming messages
|
||||
// for {
|
||||
// // Read message from the client
|
||||
// _, message, err := conn.ReadMessage()
|
||||
// if err != nil {
|
||||
// fmt.Println("Error reading message:", err)
|
||||
// break
|
||||
// }
|
||||
// fmt.Printf("Received: %s\\n", message)
|
||||
// // Echo the message back to the client
|
||||
// if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
// fmt.Println("Error writing message:", err)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// abstracted out interface into this runner so i can write tests
|
||||
type CommandRunner interface {
|
||||
Run(name string, args ...string) ([]byte, error)
|
||||
}
|
||||
func wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Upgrade the HTTP connection to a WebSocket connection
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
fmt.Println("Error upgrading:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
// Listen for incoming messages
|
||||
for {
|
||||
// Read message from the client
|
||||
_, message, err := conn.ReadMessage()
|
||||
if err != nil {
|
||||
fmt.Println("Error reading message:", err)
|
||||
break
|
||||
}
|
||||
fmt.Printf("Received: %s\\n", message)
|
||||
// Echo the message back to the client
|
||||
if err := conn.WriteMessage(websocket.TextMessage, message); err != nil {
|
||||
fmt.Println("Error writing message:", err)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
type ExecRunner struct{}
|
||||
|
||||
func (ExecRunner) Run(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).Output()
|
||||
}
|
||||
|
||||
type abraHandler struct{
|
||||
mux *http.ServeMux
|
||||
cmd ExecRunner
|
||||
}
|
||||
func newAbraHandler() *abraHandler {
|
||||
h := &abraHandler{
|
||||
@@ -58,21 +69,31 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/deploy", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Header.Get("Chaos") == "true"{
|
||||
internal.Chaos = true
|
||||
} else {
|
||||
internal.Chaos = false
|
||||
}
|
||||
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
h.handleDeployApp(w, r, r.PathValue("appId"))
|
||||
h.handleDeployApp(w, r, r.PathValue("appId"), r.URL.Query().Get("chaos") == "true")
|
||||
case http.MethodGet:
|
||||
h.getDeployLogs(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/upgrade", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
h.handleUpgradeApp(w, r, r.PathValue("appId"), r.URL.Query().Get("force") == "true")
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/rollback", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
h.handleRollbackApp(w, r, r.PathValue("appId"), r.URL.Query().Get("force") == "true")
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/stop", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
@@ -105,7 +126,6 @@ func newAbraHandler() *abraHandler {
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
|
||||
h.mux.HandleFunc("/api/apps/{appId}/new", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
case http.MethodPost:
|
||||
@@ -150,6 +170,14 @@ func newAbraHandler() *abraHandler {
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/versions", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
case http.MethodGet:
|
||||
h.handleListVersions(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
return h
|
||||
}
|
||||
func StartAPI() {
|
||||
@@ -177,4 +205,4 @@ func (h *abraHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// Delegate to internal mux
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
+3
-6
@@ -1,9 +1,8 @@
|
||||
package cli
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"slices"
|
||||
"log"
|
||||
"maps"
|
||||
"encoding/json"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
@@ -20,12 +19,10 @@ func (h *abraHandler) handleListCatalogue(w http.ResponseWriter, r *http.Request
|
||||
|
||||
jsonBytes, err := json.Marshal(vals)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
}
|
||||
+3
-4
@@ -46,7 +46,7 @@ func (h *abraHandler) handleGetConfig(w http.ResponseWriter, r *http.Request, ap
|
||||
func (h *abraHandler) handlePostConfig(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
files, err := appPkg.LoadAppFiles("")
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -61,9 +61,8 @@ func (h *abraHandler) handlePostConfig(w http.ResponseWriter, r *http.Request, a
|
||||
var config FileResponse
|
||||
err = d.Decode(&config)
|
||||
if err != nil {
|
||||
log.Printf("err: %s", err);
|
||||
http.Error(w, "Failed to read request", http.StatusBadRequest)
|
||||
return
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = os.WriteFile(appFile.Path, []byte(config.Content), 0644)
|
||||
|
||||
+10
-15
@@ -2,11 +2,11 @@ package cli
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"coop-cloud-backend/internal"
|
||||
"coop-cloud-backend/cli/status"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
"context"
|
||||
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
@@ -23,17 +23,16 @@ import (
|
||||
|
||||
//"github.com/gorilla/websocket"
|
||||
)
|
||||
func (h *abraHandler) handleDeployApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
func (h *abraHandler) handleDeployApp(w http.ResponseWriter, r *http.Request, appName string, chaos bool) {
|
||||
log.Printf("Handling App Deploy!")
|
||||
args := []string{"app", "deploy", appName, "-n", "-c"}
|
||||
if internal.Chaos {
|
||||
if chaos {
|
||||
args = append(args, "-C")
|
||||
}
|
||||
cmd := exec.Command("abra", args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", string(output))
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
log.Printf("Finishing app deploy!")
|
||||
@@ -43,27 +42,25 @@ func (h *abraHandler) getDeployLogs(w http.ResponseWriter, r *http.Request, appN
|
||||
log.Printf("Get deploy logs!")
|
||||
app, err := appPkg.Get(appName)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
// Implement my own WaitOnServices in order to wrap ui.Model in order to emit JSON updates
|
||||
// over Websocket connection
|
||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
f, err := app.Filters(true, false, serviceNames...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
// STEP 1: collect information about app being deployed
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -73,8 +70,7 @@ func (h *abraHandler) getDeployLogs(w http.ResponseWriter, r *http.Request, appN
|
||||
log.Printf("stack name: %s | namespace: %s", stackName, namespace.Name())
|
||||
existingServices, err := GetStackServices(context.Background(), cl, namespace.Name())
|
||||
if err != nil {
|
||||
log.Printf("Error: %s\n", err)
|
||||
http.Error(w, fmt.Sprintf("Error: %s\n", err), http.StatusInternalServerError)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -112,8 +108,7 @@ func (h *abraHandler) getDeployLogs(w http.ResponseWriter, r *http.Request, appN
|
||||
fmt.Printf(string(test), err);
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
// TODO: send error through onerror handler
|
||||
log.Printf("error?: %s", err)
|
||||
OnError(err)
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "data: %s\n\n", b)
|
||||
|
||||
+14
-7
@@ -1,13 +1,20 @@
|
||||
package cli
|
||||
import (
|
||||
"net/http"
|
||||
"net/http"
|
||||
"log"
|
||||
)
|
||||
func InternalServerErrorHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
w.Write([]byte("500 Internal Server Error"))
|
||||
func OnError(err error) {
|
||||
// handle errors that continue
|
||||
log.Printf("Error: %v", err)
|
||||
}
|
||||
func InternalServerError(w http.ResponseWriter, err error) {
|
||||
log.Printf("Error: %v", err)
|
||||
http.Error(w, "internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
func NotFoundHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
w.Write([]byte("404 Not Found"))
|
||||
func NotFound(w http.ResponseWriter, err error) {
|
||||
http.Error(w, "not found", http.StatusNotFound)
|
||||
}
|
||||
func BadRequest(w http.ResponseWriter, err error) {
|
||||
http.Error(w, "bad request", http.StatusBadRequest)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package interface
|
||||
|
||||
import "os/exec"
|
||||
|
||||
type RealCommandRunner struct{}
|
||||
|
||||
func (r *RealCommandRunner) Run(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).Output()
|
||||
}
|
||||
+99
-20
@@ -1,12 +1,20 @@
|
||||
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"
|
||||
|
||||
)
|
||||
@@ -15,8 +23,7 @@ import (
|
||||
cmd := exec.Command("abra", "app", "ls", "-m")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
var resp ServerAppsResponse
|
||||
@@ -25,33 +32,71 @@ import (
|
||||
recipeFilter := ""
|
||||
|
||||
if err := json.Unmarshal(output, &resp); err != nil {
|
||||
http.Error(w, "invalid JSON from CLI", http.StatusInternalServerError)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("invalid JSON from CLI")))
|
||||
return
|
||||
}
|
||||
|
||||
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
apps, err := appPkg.GetApps(appFiles, recipeFilter)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
statuses := make(map[string]map[string]string)
|
||||
alreadySeen := make(map[string]bool)
|
||||
|
||||
for _, app := range apps {
|
||||
if _, ok := alreadySeen[app.Server]; !ok {
|
||||
alreadySeen[app.Server] = true
|
||||
}
|
||||
// servers that are unreachable..
|
||||
reachable := make(map[string]bool)
|
||||
servers, err := config.ReadServerNames()
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
start := time.Now()
|
||||
|
||||
statuses, err = appPkg.GetAppStatuses(apps, true)
|
||||
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
|
||||
|
||||
@@ -63,6 +108,20 @@ import (
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
@@ -96,8 +155,7 @@ import (
|
||||
var newUpdates []string
|
||||
if version != "unknown" && chaos == "false" {
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Printf("unable to clone %s: %s", app.Name, err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("unable to clone %s: %s", app.Name, err)))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -152,8 +210,7 @@ import (
|
||||
}
|
||||
jsonBytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
log.Printf("JSON conversion failed: %s\n", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("JSON conversion failed: %s\n", err)))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -164,8 +221,7 @@ func (h *abraHandler) handleListServers (w http.ResponseWriter, r *http.Request)
|
||||
cmd := exec.Command("abra", "server", "ls", "-m")
|
||||
abraServers, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -173,6 +229,29 @@ func (h *abraHandler) handleListServers (w http.ResponseWriter, r *http.Request)
|
||||
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
|
||||
|
||||
|
||||
+7
-7
@@ -16,36 +16,36 @@ import (
|
||||
func (h *abraHandler) handleGetLogs(w http.ResponseWriter, r *http.Request, appName string, serviceName string) {
|
||||
app, err := appPkg.Get(appName)
|
||||
if err != nil {
|
||||
log.Printf("error: %s", err)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
stackName := app.StackName()
|
||||
|
||||
if err := app.Recipe.EnsureExists(); err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !deployMeta.IsDeployed {
|
||||
log.Printf(fmt.Sprintf("%s is not deployed?", app.Name))
|
||||
log.Printf("%s is not deployed?", app.Name)
|
||||
return
|
||||
}
|
||||
serviceNames := []string{serviceName}
|
||||
|
||||
f, err := app.Filters(true, false, serviceNames...)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
ctx := r.Context()
|
||||
|
||||
@@ -54,7 +54,7 @@ func (h *abraHandler) handleGetLogs(w http.ResponseWriter, r *http.Request, appN
|
||||
types.ServiceListOptions{Filters: f},
|
||||
)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
+6
-5
@@ -3,8 +3,8 @@ package cli
|
||||
// Represents an App, follows the format of
|
||||
// https://git.coopcloud.tech/toolshed/coop-cloud-front
|
||||
type AbraApp struct {
|
||||
Server string `json:"server"`
|
||||
Recipe string `json:"recipe`
|
||||
Server string `json:"server"`
|
||||
Recipe string `json:"recipe`
|
||||
AppName string `json:"appName"`
|
||||
Domain string `json:"domain"`
|
||||
Status string `json:"status"`
|
||||
@@ -41,9 +41,9 @@ type DeployState struct {
|
||||
}
|
||||
|
||||
type DeployStream struct {
|
||||
Name string `json:"Name"`
|
||||
id string `json:"id"`
|
||||
status string `json:"status"`
|
||||
Name string `json:"Name"`
|
||||
id string `json:"id"`
|
||||
status string `json:"status"`
|
||||
retries int `json:"retries"`
|
||||
health string `json:"health"`
|
||||
rollback bool `json:"rollback"`
|
||||
@@ -53,6 +53,7 @@ type AppSecret struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Value string `json:"value"`
|
||||
Created bool `json:"created"`
|
||||
}
|
||||
|
||||
type AbraAppService struct {
|
||||
|
||||
+5
-6
@@ -4,6 +4,7 @@ import (
|
||||
"os/exec"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
@@ -48,16 +49,14 @@ func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appNa
|
||||
cmd := exec.Command("abra", args...)
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", string(output))
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
var fs []AppSecret
|
||||
if body.Secrets != nil && *body.Secrets == true {
|
||||
appSecrets, err := createSecrets(appName, *body.Domain, *body.Server)
|
||||
if err != nil {
|
||||
log.Printf("Error creating secrets: %s", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
for k, v := range appSecrets {
|
||||
@@ -65,13 +64,13 @@ func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appNa
|
||||
Name: k,
|
||||
Value: v,
|
||||
Version: "v1",
|
||||
Created: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
jsonBytes, err := json.Marshal(fs)
|
||||
if err != nil {
|
||||
log.Printf("JSON conversion failed: %s\n", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
+3
-7
@@ -1,7 +1,6 @@
|
||||
package cli
|
||||
import (
|
||||
"os/exec"
|
||||
"log"
|
||||
"sort"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
@@ -11,13 +10,11 @@ func (h *abraHandler) handleGetAppServices (w http.ResponseWriter, r *http.Reque
|
||||
cmd := exec.Command("abra", "app", "ps", appName, "-m")
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
if err = json.Unmarshal(output, &tmpMsg); err != nil {
|
||||
log.Printf("Error: ", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
var services []AbraAppService
|
||||
@@ -38,8 +35,7 @@ func (h *abraHandler) handleGetAppServices (w http.ResponseWriter, r *http.Reque
|
||||
})
|
||||
jsonBytes, err := json.Marshal(services)
|
||||
if err != nil {
|
||||
log.Printf("JSON conversion failed: %s\n", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
package cli
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
func (h *abraHandler) handleRemoveApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
cmd := exec.Command("abra", "app", "remove", appName, "-n")
|
||||
output, err := cmd.Output()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", string(output))
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
+48
-12
@@ -2,20 +2,60 @@ package cli
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"errors"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
func (h *abraHandler) handleGetAppSecrets(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
|
||||
cmd := exec.Command("abra", "app", "secret", "list", appName, "-m")
|
||||
secrets, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", err)
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := []struct {
|
||||
Created *string `json:"created on server"`
|
||||
GenName *string `json:"generated name"`
|
||||
Name *string `json:"name"`
|
||||
Version *string `json:"version"`
|
||||
}{}
|
||||
|
||||
err = json.Unmarshal(secrets, &resp)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
var fs []AppSecret
|
||||
for _, secret := range resp {
|
||||
if secret.Created == nil {
|
||||
InternalServerError(w, errors.New("Failed to decode field 'created on server' from abra response"))
|
||||
return
|
||||
}
|
||||
if secret.Name == nil {
|
||||
InternalServerError(w, errors.New("Failed to decode field 'name' from abra response"))
|
||||
return
|
||||
}
|
||||
if secret.Version == nil {
|
||||
InternalServerError(w, errors.New("Failed to decode field 'version' from abra response"))
|
||||
return
|
||||
}
|
||||
fs = append(fs, AppSecret{
|
||||
Name: *secret.Name,
|
||||
Version: *secret.Version,
|
||||
Created: *secret.Created == "true",
|
||||
Value: "",
|
||||
})
|
||||
}
|
||||
jsonBytes, err := json.Marshal(fs)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(secrets)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
func (h *abraHandler) handleInsertAppSecret(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
@@ -31,31 +71,27 @@ func (h *abraHandler) handleInsertAppSecret(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
err := d.Decode(&body)
|
||||
if err != nil {
|
||||
log.Printf("???\n")
|
||||
// bad JSON or unrecognized json field
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if body.Name == nil {
|
||||
http.Error(w, "missing field 'name' from JSON object", http.StatusBadRequest)
|
||||
BadRequest(w, errors.New("missing field 'name' from JSON object"))
|
||||
return
|
||||
}
|
||||
if body.Version == nil {
|
||||
http.Error(w, "missing field 'version' from JSON object", http.StatusBadRequest)
|
||||
BadRequest(w, errors.New("missing field 'version' from JSON object"))
|
||||
return
|
||||
}
|
||||
if body.Value == nil {
|
||||
http.Error(w, "missing field 'value' from JSON object", http.StatusBadRequest)
|
||||
BadRequest(w, errors.New("missing field 'value' from JSON object"))
|
||||
return
|
||||
}
|
||||
log.Printf("%s %s %s", *body.Name, *body.Version, *body.Value)
|
||||
cmd := exec.Command("abra", "app", "secret", "insert", appName, *body.Name, *body.Version, *body.Value)
|
||||
output, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
log.Printf("Error: ", string(output))
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// package ui
|
||||
// import (
|
||||
// "github.com/gorilla/websocket"
|
||||
// "time"
|
||||
// "encoding/json"
|
||||
// "github.com/docker/docker/pkg/jsonmessage"
|
||||
|
||||
// )
|
||||
// type WSMessage struct {
|
||||
// MsgType string `json:"msgType"`
|
||||
// Timestamp time.Time `json:"timestamp"`
|
||||
// Payload interface{} `json:"payload"`
|
||||
// }
|
||||
// type DonePayload struct {
|
||||
// Success bool `json:"success"`
|
||||
// Failed bool `json:"failed"`
|
||||
// TimedOut bool `json:"timed_out"`
|
||||
// }
|
||||
|
||||
// func makeWsEmit(conn *websocket.Conn) func([]byte) error {
|
||||
// return func(b []byte) error {
|
||||
// if conn == nil {
|
||||
// return nil
|
||||
// }
|
||||
// // Send as a text message
|
||||
// return conn.WriteMessage(websocket.TextMessage, b)
|
||||
// }
|
||||
// }
|
||||
// func send(wsEmit func([]byte) error, msgType string, payload interface{}) {
|
||||
// if wsEmit == nil {
|
||||
// return
|
||||
// }
|
||||
// msg := WSMessage{
|
||||
// MsgType: msgType,
|
||||
// Timestamp: time.Now().UTC(),
|
||||
// Payload: payload,
|
||||
// }
|
||||
|
||||
// b, _ := json.Marshal(msg)
|
||||
// _ = wsEmit(b)
|
||||
// }
|
||||
+4
-6
@@ -1,15 +1,13 @@
|
||||
package cli
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
func (h *abraHandler) handleUndeployApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
cmd := exec.Command("abra", "app", "undeploy", appName, "-n")
|
||||
output, err := cmd.Output()
|
||||
output, err := h.cmd.Run("abra", "app", "undeploy", appName, "-n")
|
||||
if err != nil {
|
||||
log.Printf("Error: ", string(output))
|
||||
InternalServerErrorHandler(w, r)
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
package cli
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h* abraHandler) handleUpgradeApp(w http.ResponseWriter, r *http.Request, appName string, force bool) {
|
||||
log.Printf("Handling App Upgrade!")
|
||||
|
||||
args := []string{"app", "upgrade", appName, "-n", "-c"}
|
||||
if force {
|
||||
args = append(args, "-f")
|
||||
}
|
||||
output, err := h.cmd.Run("abra", args...)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
log.Printf("Finishing app upgrade!")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h* abraHandler) handleRollbackApp(w http.ResponseWriter, r *http.Request, appName string, force bool) {
|
||||
log.Printf("Handling App rollback!")
|
||||
|
||||
d := json.NewDecoder(r.Body)
|
||||
d.DisallowUnknownFields()
|
||||
|
||||
body := struct {
|
||||
Version *string `json:"version"`
|
||||
}{}
|
||||
err := d.Decode(&body)
|
||||
if err != nil {
|
||||
log.Printf("error: %v", err)
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
args := []string{"app", "rollback", appName, "-n", "-c"}
|
||||
if force {
|
||||
args = append(args, "-f")
|
||||
}
|
||||
if body.Version != nil{
|
||||
if *body.Version == "Auto" {
|
||||
log.Printf("leaving abra to determine rollback version")
|
||||
} else {
|
||||
args = append(args, *body.Version)
|
||||
}
|
||||
}
|
||||
|
||||
output, err := h.cmd.Run("abra", args...)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
log.Printf("Finishing app rollback!")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package cli
|
||||
import (
|
||||
"testing"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"errors"
|
||||
"reflect"
|
||||
)
|
||||
type MockRunner struct {
|
||||
Name string
|
||||
Args []string
|
||||
|
||||
OutputBytes []byte
|
||||
Err error
|
||||
}
|
||||
|
||||
func (m *MockRunner) Run(name string, args ...string) ([]byte, error) {
|
||||
m.Name = name
|
||||
m.Args = args
|
||||
|
||||
return m.OutputBytes, m.Err
|
||||
}
|
||||
|
||||
func TestHandleUpgradeApp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
appName string
|
||||
force bool
|
||||
mockErr error
|
||||
expectedStatus int
|
||||
expectedArgs []string
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
appName: "myapp",
|
||||
force: false,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArgs: []string{
|
||||
"app",
|
||||
"upgrade",
|
||||
"myapp",
|
||||
"-n",
|
||||
"-c",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "force success",
|
||||
appName: "myapp",
|
||||
force: true,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArgs: []string{
|
||||
"app",
|
||||
"upgrade",
|
||||
"myapp",
|
||||
"-n",
|
||||
"-c",
|
||||
"-f",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "command failure",
|
||||
appName: "myapp",
|
||||
force: false,
|
||||
mockErr: errors.New("dead"),
|
||||
expectedStatus: http.StatusInternalServerError,
|
||||
expectedArgs: []string{
|
||||
"app",
|
||||
"upgrade",
|
||||
"myapp",
|
||||
"-n",
|
||||
"-c",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := &MockRunner{
|
||||
Err: tt.mockErr,
|
||||
}
|
||||
|
||||
h := &abraHandler{
|
||||
cmd: mockCmd,
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
h.handleUpgradeApp(w, req, tt.appName, tt.force)
|
||||
|
||||
res := w.Result()
|
||||
|
||||
if res.StatusCode != tt.expectedStatus {
|
||||
t.Fatalf(
|
||||
"expected status %d, got %d",
|
||||
tt.expectedStatus,
|
||||
res.StatusCode,
|
||||
)
|
||||
}
|
||||
|
||||
if mockCmd.Name != "abra" {
|
||||
t.Fatalf(
|
||||
"expected command abra, got %s",
|
||||
mockCmd.Name,
|
||||
)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(mockCmd.Args, tt.expectedArgs) {
|
||||
t.Fatalf(
|
||||
"expected args %v, got %v",
|
||||
tt.expectedArgs,
|
||||
mockCmd.Args,
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,4 +4,4 @@ export PATH=/home/node/.local/bin/:$PATH
|
||||
|
||||
/home/node/wizard/gobackend &
|
||||
|
||||
npm run dev -- --host
|
||||
npm run dev -- --host 0.0.0.0 --port 5173
|
||||
|
||||
@@ -102,6 +102,7 @@ require (
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
github.com/prometheus/procfs v0.17.0 // indirect
|
||||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/samber/lo v1.53.0 // indirect
|
||||
github.com/schollz/progressbar/v3 v3.18.0 // indirect
|
||||
github.com/sergi/go-diff v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
|
||||
@@ -462,6 +462,8 @@ github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99
|
||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||
github.com/schollz/progressbar/v3 v3.14.4 h1:W9ZrDSJk7eqmQhd3uxFNNcTr0QL+xuGNI9dEMrw0r74=
|
||||
github.com/schollz/progressbar/v3 v3.14.4/go.mod h1:aT3UQ7yGm+2ZjeXPqsjTenwL3ddUiuZ0kfQ/2tHlyNI=
|
||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
||||
|
||||
Reference in New Issue
Block a user