Merge branch 'jjsfunhouse-backup'
This commit is contained in:
+86
-26
@@ -1,11 +1,15 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"coop-cloud-backend/cli/external"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// // Upgrader is used to upgrade HTTP connections to WebSocket connections.
|
||||
// var upgrader = websocket.Upgrader{
|
||||
// CheckOrigin: func(r *http.Request) bool {
|
||||
@@ -37,6 +41,20 @@ import (
|
||||
// }
|
||||
// }
|
||||
|
||||
type MockCommandRunner struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (m *MockCommandRunner) Run(name string, args ...string) ([]byte, error) {
|
||||
callArgs := []interface{}{name}
|
||||
for _, arg := range args {
|
||||
callArgs = append(callArgs, arg)
|
||||
}
|
||||
|
||||
resp := m.Called(callArgs...)
|
||||
return resp.Get(0).([]byte), resp.Error(1)
|
||||
}
|
||||
|
||||
// abstracted out interface into this runner so i can write tests
|
||||
type CommandRunner interface {
|
||||
Run(name string, args ...string) ([]byte, error)
|
||||
@@ -45,16 +63,26 @@ type CommandRunner interface {
|
||||
type ExecRunner struct{}
|
||||
|
||||
func (ExecRunner) Run(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).Output()
|
||||
return exec.Command(name, args...).CombinedOutput()
|
||||
}
|
||||
|
||||
type abraHandler struct{
|
||||
mux *http.ServeMux
|
||||
cmd ExecRunner
|
||||
type abraHandler struct {
|
||||
mux *http.ServeMux
|
||||
cmd CommandRunner
|
||||
secret SecretUtilsWrapper
|
||||
ap external.AppPackage
|
||||
sp external.ServerPackage
|
||||
rp external.RecipePackage
|
||||
}
|
||||
|
||||
func newAbraHandler() *abraHandler {
|
||||
h := &abraHandler{
|
||||
mux: http.NewServeMux(),
|
||||
mux: http.NewServeMux(),
|
||||
cmd: ExecRunner{},
|
||||
secret: &SecretUtils{},
|
||||
ap: &external.AbraAppPackage{},
|
||||
sp: &external.AbraServerPackage{},
|
||||
rp: &external.AbraRecipePackage{},
|
||||
}
|
||||
h.mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
log.Printf("API Path not found")
|
||||
@@ -62,14 +90,14 @@ func newAbraHandler() *abraHandler {
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
case http.MethodGet:
|
||||
h.handleListApps(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/deploy", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleDeployApp(w, r, r.PathValue("appId"), r.URL.Query().Get("chaos") == "true")
|
||||
case http.MethodGet:
|
||||
@@ -79,7 +107,7 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/upgrade", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleUpgradeApp(w, r, r.PathValue("appId"), r.URL.Query().Get("force") == "true")
|
||||
default:
|
||||
@@ -87,15 +115,23 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/rollback", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
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}/backup/create", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleBackupCreateApp(w, r, r.PathValue("appId"))
|
||||
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{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleUndeployApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
@@ -103,7 +139,7 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/remove", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleRemoveApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
@@ -112,7 +148,7 @@ func newAbraHandler() *abraHandler {
|
||||
})
|
||||
h.mux.HandleFunc("/api/servers", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
case http.MethodGet:
|
||||
h.handleListServers(w, r)
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
@@ -120,14 +156,14 @@ func newAbraHandler() *abraHandler {
|
||||
})
|
||||
h.mux.HandleFunc("/api/catalogue", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
case http.MethodGet:
|
||||
h.handleListCatalogue(w, r)
|
||||
default:
|
||||
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{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleNewApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
@@ -135,7 +171,7 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/config", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handlePostConfig(w, r, r.PathValue("appId"))
|
||||
case http.MethodGet:
|
||||
@@ -144,8 +180,32 @@ func newAbraHandler() *abraHandler {
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/backup/list", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleBackupListApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/backup/restore", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleBackupRestoreApp(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/backup/download", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleBackupDownloadSnapshot(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
http.Error(w, "Method not implemented", http.StatusMethodNotAllowed)
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/secret", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
h.handleInsertAppSecret(w, r, r.PathValue("appId"))
|
||||
case http.MethodGet:
|
||||
@@ -155,7 +215,7 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/services", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleGetAppServices(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
@@ -163,7 +223,7 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/{serviceId}/logs", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleGetLogs(w, r, r.PathValue("appId"), r.PathValue("serviceId"))
|
||||
default:
|
||||
@@ -171,7 +231,7 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
})
|
||||
h.mux.HandleFunc("/api/apps/{appId}/versions", func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method{
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
h.handleListVersions(w, r, r.PathValue("appId"))
|
||||
default:
|
||||
@@ -182,11 +242,11 @@ func newAbraHandler() *abraHandler {
|
||||
}
|
||||
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)
|
||||
}*/
|
||||
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")
|
||||
@@ -205,4 +265,4 @@ func (h *abraHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
// Delegate to internal mux
|
||||
h.mux.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"coopcloud.tech/abra/pkg/i18n"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath string) (io.ReadCloser, int64, error) {
|
||||
log.Printf("%v %v", containerID, srcPath)
|
||||
content, stat, err := cl.CopyFromContainer(context.Background(), containerID, srcPath)
|
||||
if err != nil {
|
||||
return nil, 0, errors.New(i18n.G("copy: %s", err))
|
||||
}
|
||||
return content, stat.Size, nil
|
||||
}
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
// used for replacing abra code
|
||||
"coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||
"coopcloud.tech/abra/pkg/i18n"
|
||||
"coopcloud.tech/abra/pkg/service"
|
||||
"github.com/docker/docker/api/types"
|
||||
containertypes "github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
)
|
||||
|
||||
func (h *abraHandler) pingBackupBot(w http.ResponseWriter, r *http.Request, server string) {
|
||||
cl, err := client.New(server)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
_, err = RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
func (h *abraHandler) handleBackupCreateApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("Handling App Backup!")
|
||||
args := []string{}
|
||||
|
||||
_, err := handleBackupCommand(appName, args, "create")
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
log.Printf("Finishing app backup!")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *abraHandler) handleBackupRestoreApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("Handling Backup Restore!")
|
||||
|
||||
args := []string{}
|
||||
d := json.NewDecoder(r.Body)
|
||||
d.DisallowUnknownFields()
|
||||
|
||||
body := struct {
|
||||
Snapshot *string `json:"snapshot"`
|
||||
}{}
|
||||
err := d.Decode(&body)
|
||||
if err != nil {
|
||||
log.Printf("error: %v", err)
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
if body.Snapshot != nil {
|
||||
args = append(args, fmt.Sprintf("SNAPSHOT=%s", *body.Snapshot))
|
||||
}
|
||||
args = append(args, fmt.Sprintf("NONINTERACTIVE=%v", true))
|
||||
_, err = handleBackupCommand(appName, args, "restore")
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
log.Printf("Finishing app restore!")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h *abraHandler) handleBackupListApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("Handling App List Backup!")
|
||||
args := []string{}
|
||||
output, err := handleBackupCommand(appName, args, "snapshots")
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
snapshots := strings.Split(output, "\n")
|
||||
resp := []AppSnapshot{}
|
||||
for _, snapshot := range snapshots {
|
||||
items := strings.Split(snapshot, " ")
|
||||
if len(items) < 3 {
|
||||
break
|
||||
}
|
||||
resp = append(resp, AppSnapshot{
|
||||
CreatedAt: items[0],
|
||||
Snapshot: items[1],
|
||||
Version: items[2],
|
||||
})
|
||||
}
|
||||
jsonBytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
func (h *abraHandler) handleBackupDownloadSnapshot(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("Handling Snapshot Download!")
|
||||
app, err := app.Get(appName)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
targetContainer, err := RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
execEnv := []string{
|
||||
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||
"MACHINE_LOGS=true",
|
||||
}
|
||||
// TODO: add args
|
||||
_, err = RunBackupCmdRemote(cl, "download", targetContainer.ID, execEnv)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
|
||||
remoteBackupDir := "/tmp/backup.tar.gz"
|
||||
content, size, err := CopyFromContainer(cl, targetContainer.ID, remoteBackupDir)
|
||||
if err != nil {
|
||||
InternalServerError(w, fmt.Errorf("error copying from container: %v", err))
|
||||
}
|
||||
defer content.Close()
|
||||
|
||||
w.Header().Set("Content-type", "application/x-tar")
|
||||
tr := tar.NewReader(content)
|
||||
for {
|
||||
header, err := tr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
if filepath.Base(header.Name) == "backup.tar.gz" {
|
||||
w.Header().Set(
|
||||
"Content-Disposition",
|
||||
`attachment; filename="backup.tar.gz"`,
|
||||
)
|
||||
w.Header().Set("Content-Type", "application/gzip")
|
||||
w.Header().Set(
|
||||
"Content-Length",
|
||||
strconv.FormatInt(size, 10),
|
||||
)
|
||||
if _, err := io.Copy(w, tr); err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func handleBackupCommand(appName string, args []string, action string) (string, error) {
|
||||
app, err := app.Get(appName)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
cl, err := client.New(app.Server)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
targetContainer, err := RetrieveBackupBotContainer(cl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
execEnv := []string{
|
||||
fmt.Sprintf("SERVICE=%s", app.Domain),
|
||||
"MACHINE_LOGS=true",
|
||||
}
|
||||
for _, arg := range args {
|
||||
execEnv = append(execEnv, arg)
|
||||
}
|
||||
output, err := RunBackupCmdRemote(cl, action, targetContainer.ID, execEnv)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// copied from https://git.coopcloud.tech/toolshed/abra/src/branch/main/cli/internal/backup.go
|
||||
// tty is set to false
|
||||
func RunBackupCmdRemote(
|
||||
cl *dockerClient.Client,
|
||||
backupCmd string,
|
||||
containerID string,
|
||||
execEnv []string) (string, error) {
|
||||
execBackupListOpts := containertypes.ExecOptions{
|
||||
AttachStderr: true,
|
||||
AttachStdout: true,
|
||||
Cmd: []string{"/usr/bin/backup", "--", backupCmd},
|
||||
Env: execEnv,
|
||||
Tty: false,
|
||||
}
|
||||
|
||||
stdout, err := Exec(context.Background(), cl, containerID, &execBackupListOpts)
|
||||
log.Printf("stdout: %v | err: %v", stdout, err)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return stdout, nil
|
||||
}
|
||||
|
||||
func RetrieveBackupBotContainer(cl *dockerClient.Client) (types.Container, error) {
|
||||
ctx := context.Background()
|
||||
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, true)
|
||||
if err != nil {
|
||||
return types.Container{}, errors.New(i18n.G("no backupbot discovered, is it deployed?"))
|
||||
}
|
||||
|
||||
filters := filters.NewArgs()
|
||||
filters.Add("name", chosenService.Spec.Name)
|
||||
targetContainer, err := containerPkg.GetContainer(
|
||||
ctx,
|
||||
cl,
|
||||
filters,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return types.Container{}, err
|
||||
}
|
||||
|
||||
return targetContainer, nil
|
||||
}
|
||||
|
||||
func Exec(ctx context.Context, cli *dockerClient.Client, containerID string, execOptions *container.ExecOptions) (string, error) {
|
||||
execResp, err := cli.ContainerExecCreate(ctx, containerID, *execOptions)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
resp, err := cli.ContainerExecAttach(ctx, execResp.ID, container.ExecStartOptions{
|
||||
Tty: false,
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Close()
|
||||
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
_, err = stdcopy.StdCopy(&stdout, &stderr, resp.Reader)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
inspect, err := cli.ContainerExecInspect(ctx, execResp.ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if inspect.ExitCode != 0 {
|
||||
return stdout.String(),
|
||||
fmt.Errorf("command exited with status %d and error %s", inspect.ExitCode, stderr.String())
|
||||
}
|
||||
|
||||
return stdout.String(), nil
|
||||
}
|
||||
+35
-30
@@ -1,36 +1,34 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"coop-cloud-backend/cli/status"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
"context"
|
||||
"coop-cloud-backend/cli/status"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
//"coopcloud.tech/abra/pkg/upstream/stack"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"coopcloud.tech/abra/pkg/ui"
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
dclient "github.com/docker/docker/client"
|
||||
"github.com/docker/docker/api/types"
|
||||
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
|
||||
//"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
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 chaos {
|
||||
args = append(args, "-C")
|
||||
}
|
||||
cmd := exec.Command("abra", args...)
|
||||
output, err := cmd.Output()
|
||||
output, err := h.cmd.Run("abra", args...)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
@@ -38,9 +36,17 @@ func (h *abraHandler) handleDeployApp(w http.ResponseWriter, r *http.Request, ap
|
||||
log.Printf("Finishing app deploy!")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
func (h *abraHandler) handleUndeployApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
output, err := h.cmd.Run("abra", "app", "undeploy", appName, "-n")
|
||||
if err != nil {
|
||||
InternalServerError(w, fmt.Errorf("Error: %s", string(output)))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
func (h *abraHandler) getDeployLogs(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("Get deploy logs!")
|
||||
app, err := appPkg.Get(appName)
|
||||
app, err := appPkg.Get(appName)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
@@ -78,34 +84,34 @@ func (h *abraHandler) getDeployLogs(w http.ResponseWriter, r *http.Request, appN
|
||||
log.Printf("existingServices contains: %s", service.Spec.Name)
|
||||
serviceIDs = append(serviceIDs, ui.ServiceMeta{
|
||||
Name: service.Spec.Name,
|
||||
ID: service.ID,
|
||||
ID: service.ID,
|
||||
})
|
||||
}
|
||||
ctx := r.Context()
|
||||
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
log.Printf("Waiting on service...")
|
||||
|
||||
stream := make (chan status.StreamEvent, 50)
|
||||
|
||||
stream := make(chan status.StreamEvent, 50)
|
||||
go status.WaitOnServices(ctx, cl, serviceIDs, f, stream)
|
||||
for {
|
||||
select{
|
||||
case <- ctx.Done():
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Printf("deploy cancelled or done")
|
||||
return
|
||||
case msg, ok := <- stream:
|
||||
case msg, ok := <-stream:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
test, err := json.MarshalIndent(msg, "", " ")
|
||||
fmt.Printf(string(test), err);
|
||||
fmt.Printf(string(test), err)
|
||||
b, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
OnError(err)
|
||||
@@ -119,7 +125,6 @@ func (h *abraHandler) getDeployLogs(w http.ResponseWriter, r *http.Request, appN
|
||||
|
||||
}
|
||||
|
||||
|
||||
func getStackFilter(namespace string) filters.Args {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", convert.LabelNamespace+"="+namespace)
|
||||
@@ -131,4 +136,4 @@ func getStackServiceFilter(namespace string) filters.Args {
|
||||
|
||||
func GetStackServices(ctx context.Context, dockerclient dclient.APIClient, namespace string) ([]swarm.Service, error) {
|
||||
return dockerclient.ServiceList(ctx, types.ServiceListOptions{Filters: getStackServiceFilter(namespace)})
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+64
@@ -0,0 +1,64 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type AppPackage interface {
|
||||
GetApps(recipeFilter string, servers ...string) ([]appPkg.App, error)
|
||||
GetAppStatuses(apps []appPkg.App, MachineReadable bool) (map[string]map[string]string, error)
|
||||
StackName(app appPkg.App) string
|
||||
}
|
||||
|
||||
type AbraAppPackage struct{}
|
||||
|
||||
func (a *AbraAppPackage) GetApps(recipeFilter string, servers ...string) ([]appPkg.App, error) {
|
||||
appFiles, err := appPkg.LoadAppFiles(servers...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
apps, err := appPkg.GetApps(appFiles, recipeFilter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
func (a *AbraAppPackage) GetAppStatuses(apps []appPkg.App, MachineReadable bool) (map[string]map[string]string, error) {
|
||||
return appPkg.GetAppStatuses(apps, MachineReadable)
|
||||
}
|
||||
|
||||
func (a *AbraAppPackage) StackName(app appPkg.App) string {
|
||||
return app.StackName()
|
||||
}
|
||||
|
||||
type MockAppPackage struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (a *MockAppPackage) GetApps(recipeFilter string, servers ...string) ([]appPkg.App, error) {
|
||||
callArgs := []interface{}{recipeFilter}
|
||||
for _, arg := range servers {
|
||||
callArgs = append(callArgs, arg)
|
||||
}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).([]appPkg.App), args.Error(1)
|
||||
|
||||
}
|
||||
|
||||
func (a *MockAppPackage) GetAppStatuses(apps []appPkg.App, MachineReadable bool) (map[string]map[string]string, error) {
|
||||
callArgs := []interface{}{}
|
||||
for _, arg := range apps {
|
||||
callArgs = append(callArgs, arg)
|
||||
}
|
||||
callArgs = append(callArgs, MachineReadable)
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).(map[string]map[string]string), args.Error(1)
|
||||
}
|
||||
|
||||
func (a *MockAppPackage) StackName(app appPkg.App) string {
|
||||
callArgs := []interface{}{app}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).(string)
|
||||
}
|
||||
Vendored
+55
@@ -0,0 +1,55 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||
"github.com/docker/docker/api/types"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/swarm"
|
||||
dockerClient "github.com/docker/docker/client"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type DockerClient interface {
|
||||
GetStackServices(ctx context.Context, dockerclient dockerClient.APIClient, namespace string) ([]swarm.Service, error)
|
||||
}
|
||||
type AbraDockerClient struct{}
|
||||
|
||||
func (a *AbraDockerClient) GetStackServices(ctx context.Context, dockerclient dockerClient.APIClient, namespace string) ([]swarm.Service, error) {
|
||||
return dockerclient.ServiceList(ctx, types.ServiceListOptions{Filters: a.getStackServiceFilter(namespace)})
|
||||
}
|
||||
|
||||
func (a *AbraDockerClient) getStackFilter(namespace string) filters.Args {
|
||||
filter := filters.NewArgs()
|
||||
filter.Add("label", convert.LabelNamespace+"="+namespace)
|
||||
return filter
|
||||
}
|
||||
|
||||
func (a *AbraDockerClient) getStackServiceFilter(namespace string) filters.Args {
|
||||
return a.getStackFilter(namespace)
|
||||
}
|
||||
|
||||
type MockDockerClient struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (a *MockDockerClient) GetStackServices(ctx context.Context, dockerclient dockerClient.APIClient, namespace string) ([]swarm.Service, error) {
|
||||
callArgs := []interface{}{ctx}
|
||||
callArgs = append(callArgs, dockerclient)
|
||||
callArgs = append(callArgs, namespace)
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).([]swarm.Service), args.Error(1)
|
||||
}
|
||||
|
||||
func (a *MockDockerClient) getStackFilter(namespace string) filters.Args {
|
||||
callArgs := []interface{}{namespace}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).(filters.Args)
|
||||
}
|
||||
|
||||
func (a *MockDockerClient) getStackServiceFilter(namespace string) filters.Args {
|
||||
callArgs := []interface{}{namespace}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).(filters.Args)
|
||||
}
|
||||
Vendored
+35
@@ -0,0 +1,35 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type RecipePackage interface {
|
||||
EnsureExists(appPkg.App) error
|
||||
Tags(appPkg.App) ([]string, error)
|
||||
}
|
||||
|
||||
type AbraRecipePackage struct{}
|
||||
|
||||
func (a *AbraRecipePackage) EnsureExists(app appPkg.App) error {
|
||||
return app.Recipe.EnsureExists()
|
||||
}
|
||||
func (a *AbraRecipePackage) Tags(app appPkg.App) ([]string, error) {
|
||||
return app.Recipe.Tags()
|
||||
}
|
||||
|
||||
type MockRecipePackage struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (a *MockRecipePackage) EnsureExists(app appPkg.App) error {
|
||||
callArgs := []interface{}{app}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Error(0)
|
||||
}
|
||||
func (a *MockRecipePackage) Tags(app appPkg.App) ([]string, error) {
|
||||
callArgs := []interface{}{app}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).([]string), args.Error(1)
|
||||
}
|
||||
Vendored
+72
@@ -0,0 +1,72 @@
|
||||
package external
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type ServerPackage interface {
|
||||
GetReachableServers(apps []appPkg.App) (map[string]bool, error)
|
||||
}
|
||||
|
||||
type AbraServerPackage struct{}
|
||||
|
||||
func (a *AbraServerPackage) GetReachableServers(apps []appPkg.App) (map[string]bool, error) {
|
||||
// servers that are unreachable..
|
||||
reachable := make(map[string]bool)
|
||||
servers, err := config.ReadServerNames()
|
||||
if err != nil {
|
||||
return nil, 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()
|
||||
timeout := client.WithTimeout(3)
|
||||
if _, err := client.New(server, timeout); err != nil {
|
||||
//errCh <- err
|
||||
} else {
|
||||
reachableCh <- server
|
||||
}
|
||||
}(server)
|
||||
}
|
||||
go func() {
|
||||
for server := range reachableCh {
|
||||
reachable[server] = true
|
||||
}
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
elapsed := time.Since(start)
|
||||
log.Printf("Checking server availability took %s", elapsed)
|
||||
return reachable, nil
|
||||
}
|
||||
|
||||
type MockServerPackage struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (a *MockServerPackage) GetReachableServers(apps []appPkg.App) (map[string]bool, error) {
|
||||
callArgs := []interface{}{}
|
||||
for _, arg := range apps {
|
||||
callArgs = append(callArgs, arg)
|
||||
}
|
||||
args := a.Called(callArgs...)
|
||||
return args.Get(0).(map[string]bool), args.Error(1)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package interface
|
||||
|
||||
import "os/exec"
|
||||
|
||||
type RealCommandRunner struct{}
|
||||
|
||||
func (r *RealCommandRunner) Run(name string, args ...string) ([]byte, error) {
|
||||
return exec.Command(name, args...).Output()
|
||||
}
|
||||
+40
-78
@@ -1,27 +1,22 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os/exec"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"sort"
|
||||
"github.com/samber/lo"
|
||||
"sync"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
client "coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/config"
|
||||
"coopcloud.tech/tagcmp"
|
||||
"github.com/samber/lo"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/tagcmp"
|
||||
)
|
||||
|
||||
func (h *abraHandler) handleListApps(w http.ResponseWriter, r *http.Request) {
|
||||
cmd := exec.Command("abra", "app", "ls", "-m")
|
||||
output, err := cmd.CombinedOutput()
|
||||
func (h *abraHandler) handleListApps(w http.ResponseWriter, r *http.Request) {
|
||||
output, err := h.cmd.Run("abra", "app", "ls", "-m")
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
@@ -32,69 +27,34 @@ import (
|
||||
recipeFilter := ""
|
||||
|
||||
if err := json.Unmarshal(output, &resp); err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("invalid JSON from CLI")))
|
||||
InternalServerError(w, fmt.Errorf("invalid JSON from CLI"))
|
||||
return
|
||||
}
|
||||
|
||||
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
||||
// Get abra apps from remote
|
||||
apps, err := h.ap.GetApps(recipeFilter, listAppServer)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
apps, err := appPkg.GetApps(appFiles, recipeFilter)
|
||||
start := time.Now()
|
||||
|
||||
// Returns map of all servers that are reachable/don't time out
|
||||
reachable, err := h.sp.GetReachableServers(apps)
|
||||
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)
|
||||
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]
|
||||
_, ok := reachable[app.Server]
|
||||
return ok
|
||||
})
|
||||
statuses, err = appPkg.GetAppStatuses(rapps, true)
|
||||
statuses := make(map[string]map[string]string)
|
||||
statuses, err = h.ap.GetAppStatuses(rapps, true)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
InternalServerError(w, err)
|
||||
}
|
||||
// TODO: if a server is unreachable, skip its apps, maybe flag something?
|
||||
for _, app := range apps {
|
||||
@@ -107,8 +67,8 @@ import (
|
||||
appStats = &sapp
|
||||
}
|
||||
}
|
||||
|
||||
if !reachable[app.Server] {
|
||||
_, ok := reachable[app.Server]
|
||||
if !ok {
|
||||
appStats.Chaos = "unknown"
|
||||
appStats.ChaosVersion = "unknown"
|
||||
appStats.Version = "unknown"
|
||||
@@ -121,12 +81,14 @@ import (
|
||||
resp[app.Server] = stats
|
||||
continue
|
||||
}
|
||||
|
||||
if app.Recipe.Name == "backup-bot-two" {
|
||||
stats.BackupEnabled = true
|
||||
}
|
||||
status := "unknown"
|
||||
version := "unknown"
|
||||
chaos := "unknown"
|
||||
chaosVersion := "unknown"
|
||||
if statusMeta, ok := statuses[app.StackName()]; ok {
|
||||
if statusMeta, ok := statuses[h.ap.StackName(app)]; ok {
|
||||
if currentVersion, exists := statusMeta["version"]; exists {
|
||||
if currentVersion != "" {
|
||||
version = currentVersion
|
||||
@@ -145,6 +107,7 @@ import (
|
||||
} else {
|
||||
stats.UnversionedCount++
|
||||
}
|
||||
stats.AppCount++
|
||||
|
||||
appStats.Status = status
|
||||
appStats.Chaos = chaos
|
||||
@@ -154,12 +117,12 @@ import (
|
||||
|
||||
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)))
|
||||
if err := h.rp.EnsureExists(app); err != nil {
|
||||
InternalServerError(w, fmt.Errorf("unable to clone %s: %s", app.Name, err))
|
||||
return
|
||||
}
|
||||
|
||||
updates, err := app.Recipe.Tags()
|
||||
updates, err := h.rp.Tags(app)
|
||||
if err != nil {
|
||||
localApp = false
|
||||
} else {
|
||||
@@ -189,7 +152,7 @@ import (
|
||||
if len(newUpdates) == 0 {
|
||||
if version == "unknown" {
|
||||
appStats.Upgrade = "unknown"
|
||||
} else if localApp {
|
||||
} else if localApp {
|
||||
appStats.Upgrade = "latest"
|
||||
stats.LatestCount++
|
||||
} else {
|
||||
@@ -200,7 +163,7 @@ import (
|
||||
appStats.Upgrade = strings.Join(newUpdates, "\n")
|
||||
stats.UpgradeCount++
|
||||
}
|
||||
|
||||
|
||||
for i, sapp := range stats.Apps {
|
||||
if sapp.AppName == app.Name {
|
||||
stats.Apps[i] = *appStats
|
||||
@@ -210,16 +173,15 @@ import (
|
||||
}
|
||||
jsonBytes, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("JSON conversion failed: %s\n", err)))
|
||||
InternalServerError(w, fmt.Errorf("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()
|
||||
func (h *abraHandler) handleListServers(w http.ResponseWriter, r *http.Request) {
|
||||
abraServers, err := h.cmd.Run("abra", "server", "ls", "-m")
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
@@ -229,7 +191,7 @@ 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) {
|
||||
func (h *abraHandler) handleListVersions(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
app, err := appPkg.Get(appName)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"coop-cloud-backend/cli/external"
|
||||
"coop-cloud-backend/data"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func ToAny[T any](s []T) []any {
|
||||
out := make([]any, len(s))
|
||||
for i, v := range s {
|
||||
out[i] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// The mock call flow is
|
||||
// h.cmd.Run -> h.ap.getApps -> h.ap.GetReachableServers -> h.ap.GetAppStatuses
|
||||
// -(for each reachable app)> h.ap.StackName(app)
|
||||
// -(reachable apps with version != "unknown" && chaos == "false")> h.rp.EnsureExists(app) and h.rp.Tags(app)
|
||||
func TestListApps(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
// these are all of our "external" package function calls
|
||||
getAppsArgs []string
|
||||
getAppsOut []appPkg.App
|
||||
getAppsErr error
|
||||
getServsArgs []appPkg.App
|
||||
getServsOut map[string]bool
|
||||
getServsErr error
|
||||
getStatusesArgs []interface{}
|
||||
getStatusesOut map[string]map[string]string
|
||||
getStatusesErr error
|
||||
stackNameArgs []appPkg.App
|
||||
stackNameOut []string
|
||||
// these are arrays for all apps
|
||||
ensureArgs []appPkg.App
|
||||
ensureErrs []error
|
||||
tagsArgs []appPkg.App
|
||||
tagsOuts [][]string
|
||||
tagsErrs []error
|
||||
// these are the details for h.cmd.Run's output. Luckily the input isalways fixed
|
||||
reqBody string
|
||||
runOutput []byte
|
||||
runErr error
|
||||
// this is the final information i guess, but i need the final json thingy too
|
||||
expectedBody ServerAppsResponse
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
name: "success one simple app",
|
||||
getAppsArgs: []string{"", ""},
|
||||
getAppsOut: []appPkg.App{data.App()},
|
||||
getServsArgs: []appPkg.App{data.App()},
|
||||
getServsOut: map[string]bool{"example.com": true},
|
||||
getStatusesArgs: []interface{}{data.App(), true},
|
||||
getStatusesOut: data.AppStatus(),
|
||||
stackNameArgs: []appPkg.App{data.App()},
|
||||
stackNameOut: []string{"nextcloud"},
|
||||
ensureArgs: []appPkg.App{data.App()},
|
||||
ensureErrs: []error{nil},
|
||||
tagsArgs: []appPkg.App{data.App()},
|
||||
tagsOuts: [][]string{{"1.2.0"}},
|
||||
tagsErrs: []error{nil},
|
||||
|
||||
runOutput: []byte(`
|
||||
{
|
||||
"example.com": {
|
||||
"apps": [{
|
||||
"server": "example.com",
|
||||
"recipe": "nextcloud",
|
||||
"appName": "nextcloud.example.com",
|
||||
"domain": "nextcloud.example.com",
|
||||
"status": "",
|
||||
"chaos": "",
|
||||
"chaosVersion": "",
|
||||
"version": "",
|
||||
"upgrade": ""
|
||||
}]
|
||||
}
|
||||
}
|
||||
`),
|
||||
expectedCode: http.StatusOK,
|
||||
expectedBody: ServerAppsResponse{
|
||||
"example.com": {
|
||||
Apps: []AbraApp{AbraApp{
|
||||
Server: "example.com",
|
||||
Recipe: "nextcloud",
|
||||
AppName: "nextcloud.example.com",
|
||||
Domain: "nextcloud.example.com",
|
||||
Status: "running",
|
||||
Chaos: "false",
|
||||
ChaosVersion: "unknown",
|
||||
Version: "1.0.0",
|
||||
Upgrade: "1.2.0",
|
||||
}},
|
||||
AppCount: 1,
|
||||
VersionCount: 1,
|
||||
UnversionedCount: 0,
|
||||
LatestCount: 0,
|
||||
UpgradeCount: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := new(MockCommandRunner)
|
||||
mockAppPkg := new(external.MockAppPackage)
|
||||
mockServerPkg := new(external.MockServerPackage)
|
||||
mockRecipePkg := new(external.MockRecipePackage)
|
||||
|
||||
mockCmd.
|
||||
On("Run", "abra", "app", "ls", "-m").
|
||||
Return(tt.runOutput, tt.runErr)
|
||||
|
||||
mockAppPkg.
|
||||
On("GetApps", ToAny(tt.getAppsArgs)...).
|
||||
Return(tt.getAppsOut, tt.getAppsErr)
|
||||
|
||||
mockServerPkg.
|
||||
On("GetReachableServers", ToAny(tt.getServsArgs)...).
|
||||
Return(tt.getServsOut, tt.getServsErr)
|
||||
|
||||
mockAppPkg.
|
||||
On("GetAppStatuses", tt.getStatusesArgs...).
|
||||
Return(tt.getStatusesOut, tt.getStatusesErr)
|
||||
|
||||
for i := range tt.stackNameArgs {
|
||||
mockAppPkg.
|
||||
On("StackName", []interface{}{tt.stackNameArgs[i]}...).
|
||||
Return(tt.stackNameOut[i])
|
||||
}
|
||||
|
||||
for i, ensureArg := range tt.ensureArgs {
|
||||
mockRecipePkg.
|
||||
On("EnsureExists", ensureArg).
|
||||
Return(tt.ensureErrs[i])
|
||||
log.Printf("tag output %v", tt.tagsOuts[i])
|
||||
mockRecipePkg.
|
||||
On("Tags", tt.tagsArgs[i]).
|
||||
Return(tt.tagsOuts[i], tt.tagsErrs[i])
|
||||
}
|
||||
|
||||
h := &abraHandler{cmd: mockCmd, ap: mockAppPkg, sp: mockServerPkg, rp: mockRecipePkg}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleListApps(rec, req)
|
||||
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
var got ServerAppsResponse
|
||||
if assert.NoError(t, json.Unmarshal(rec.Body.Bytes(), &got)) {
|
||||
assert.Equal(t, tt.expectedBody, got)
|
||||
}
|
||||
|
||||
// may need to have "expectedRun" flags later
|
||||
|
||||
mockCmd.AssertExpectations(t)
|
||||
mockAppPkg.AssertExpectations(t)
|
||||
mockServerPkg.AssertExpectations(t)
|
||||
mockRecipePkg.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
+50
-43
@@ -1,70 +1,77 @@
|
||||
package cli
|
||||
|
||||
// Represents an App, follows the format of
|
||||
// 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"`
|
||||
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"`
|
||||
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"`
|
||||
Apps []AbraApp `json:"apps"`
|
||||
AppCount int `json:"appCount"`
|
||||
VersionCount int `json:"versionCount"`
|
||||
UnversionedCount int `json:"unversionedCount"`
|
||||
LatestCount int `json:"latestCount"`
|
||||
UpgradeCount int `json:"upgradeCount"`
|
||||
BackupEnabled bool `json:"backupEnabled"`
|
||||
}
|
||||
|
||||
type DeployState struct {
|
||||
AppName string `json:"appName"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
Name string `json:"Name"`
|
||||
id string `json:"id"`
|
||||
status string `json:"status"`
|
||||
retries int `json:"retries"`
|
||||
health string `json:"health"`
|
||||
rollback bool `json:"rollback"`
|
||||
}
|
||||
|
||||
type AppSecret struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Value string `json:"value"`
|
||||
Created bool `json:"created"`
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version"`
|
||||
Value string `json:"value"`
|
||||
Created bool `json:"created"`
|
||||
}
|
||||
|
||||
type AbraAppService struct {
|
||||
Service string `json:"service"`
|
||||
Chaos bool `json:"chaos"`
|
||||
Created string `json:"created"`
|
||||
Image string `json:"image"`
|
||||
Ports string `json:"ports"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
Service string `json:"service"`
|
||||
Chaos bool `json:"chaos"`
|
||||
Created string `json:"created"`
|
||||
Image string `json:"image"`
|
||||
Ports string `json:"ports"`
|
||||
State string `json:"state"`
|
||||
Status string `json:"status"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
type AppSnapshot struct {
|
||||
CreatedAt string `json:"createdAt"`
|
||||
Snapshot string `json:"snapshot"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// type RecipeMeta struct {
|
||||
|
||||
+28
-23
@@ -1,16 +1,15 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
appPkg "coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/secret"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/client"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
"coopcloud.tech/abra/pkg/secret"
|
||||
)
|
||||
|
||||
func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
@@ -20,23 +19,24 @@ func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appNa
|
||||
|
||||
// anonymous struct type: handy for one-time use
|
||||
body := struct {
|
||||
Domain *string `json:"domain"`
|
||||
Server *string `json:"server"`
|
||||
Chaos *bool `json:"chaos"`
|
||||
Secrets *bool `json:"secrets"`
|
||||
Domain *string `json:"domain"`
|
||||
Server *string `json:"server"`
|
||||
Chaos *bool `json:"chaos"`
|
||||
Secrets *bool `json:"secrets"`
|
||||
}{}
|
||||
|
||||
|
||||
err := d.Decode(&body)
|
||||
if err != nil {
|
||||
// bad JSON or unrecognized json field
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
log.Printf("%v", err)
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
if body.Domain == nil {
|
||||
if body.Domain == nil {
|
||||
http.Error(w, "missing field 'domain' from JSON object", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if body.Server == nil {
|
||||
if body.Server == nil {
|
||||
http.Error(w, "missing field 'server' from JSON object", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
@@ -46,23 +46,23 @@ func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appNa
|
||||
args = append(args, "-C")
|
||||
}
|
||||
log.Printf("%v", args)
|
||||
cmd := exec.Command("abra", args...)
|
||||
output, err := cmd.Output()
|
||||
output, err := h.cmd.Run("abra", args...)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
InternalServerError(w, fmt.Errorf("Error: %s", string(output)))
|
||||
return
|
||||
}
|
||||
var fs []AppSecret
|
||||
if body.Secrets != nil && *body.Secrets == true {
|
||||
appSecrets, err := createSecrets(appName, *body.Domain, *body.Server)
|
||||
log.Printf("testing: %v %v", *body.Domain, *body.Server)
|
||||
appSecrets, err := h.secret.createSecrets(appName, *body.Domain, *body.Server)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
for k, v := range appSecrets {
|
||||
fs = append(fs, AppSecret{
|
||||
Name: k,
|
||||
Value: v,
|
||||
Name: k,
|
||||
Value: v,
|
||||
Version: "v1",
|
||||
Created: true,
|
||||
})
|
||||
@@ -78,14 +78,19 @@ func (h *abraHandler) handleNewApp(w http.ResponseWriter, r *http.Request, appNa
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
|
||||
func createSecrets(appName string, domain string, server string) (map[string]string, error) {
|
||||
type SecretUtilsWrapper interface {
|
||||
createSecrets(appName string, domain string, server string) (map[string]string, error)
|
||||
}
|
||||
|
||||
type SecretUtils struct{}
|
||||
|
||||
func (s *SecretUtils) createSecrets(appName string, domain string, server string) (map[string]string, error) {
|
||||
cl, err := client.New(server)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recipe := recipe.Get(appName)
|
||||
|
||||
|
||||
sampleEnv, err := recipe.SampleEnv()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -111,4 +116,4 @@ func createSecrets(appName string, domain string, server string) (map[string]str
|
||||
return nil, err
|
||||
}
|
||||
return secrets, nil
|
||||
}
|
||||
}
|
||||
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
type MockSecretUtils struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
func (su *MockSecretUtils) createSecrets(appName string, domain string, server string) (map[string]string, error) {
|
||||
args := su.Called(appName, domain, server)
|
||||
return args.Get(0).(map[string]string), args.Error(1)
|
||||
|
||||
}
|
||||
func TestHandleNewApp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reqBody string
|
||||
runOutput []byte
|
||||
runErr error
|
||||
runSecrets bool
|
||||
expectedSecretArgs []string
|
||||
secretOutput map[string]string
|
||||
secretErr error
|
||||
expectedArgs []string
|
||||
expectedRun bool
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
name: "success no chaos no secrets",
|
||||
reqBody: `{
|
||||
"domain": "app.xyz",
|
||||
"server": "server.xyz",
|
||||
"chaos": false,
|
||||
"secrets": false
|
||||
}`,
|
||||
runOutput: []byte(nil),
|
||||
expectedArgs: []string{"app", "new", "myapp", "-n", "--domain=app.xyz", "--server=server.xyz"},
|
||||
expectedRun: true,
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "success with chaos no secrets",
|
||||
reqBody: `{
|
||||
"domain": "app.xyz",
|
||||
"server": "server.xyz",
|
||||
"chaos": true,
|
||||
"secrets": false
|
||||
}`,
|
||||
runOutput: []byte(nil),
|
||||
expectedArgs: []string{"app", "new", "myapp", "-n", "--domain=app.xyz", "--server=server.xyz", "-C"},
|
||||
expectedRun: true,
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "success no chaos with secrets",
|
||||
reqBody: `{
|
||||
"domain": "app.xyz",
|
||||
"server": "server.xyz",
|
||||
"chaos": false,
|
||||
"secrets": true
|
||||
}`,
|
||||
runSecrets: true,
|
||||
expectedSecretArgs: []string{"myapp", "app.xyz", "server.xyz"},
|
||||
secretOutput: map[string]string{
|
||||
"name": "secret",
|
||||
"value": "secret-value",
|
||||
},
|
||||
runOutput: []byte(nil),
|
||||
expectedArgs: []string{"app", "new", "myapp", "-n", "--domain=app.xyz", "--server=server.xyz"},
|
||||
expectedRun: true,
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := new(MockCommandRunner)
|
||||
mockSecrets := new(MockSecretUtils)
|
||||
|
||||
args := []interface{}{"abra"}
|
||||
for _, arg := range tt.expectedArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
secretArgs := []interface{}{}
|
||||
for _, arg := range tt.expectedSecretArgs {
|
||||
secretArgs = append(secretArgs, arg)
|
||||
}
|
||||
mockCmd.
|
||||
On("Run", args...).
|
||||
Return(tt.runOutput, tt.runErr)
|
||||
|
||||
if tt.runSecrets {
|
||||
mockSecrets.
|
||||
On("createSecrets", secretArgs...).
|
||||
Return(tt.secretOutput, tt.secretErr)
|
||||
}
|
||||
|
||||
h := &abraHandler{cmd: mockCmd, secret: mockSecrets}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tt.reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleNewApp(rec, req, "myapp")
|
||||
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
|
||||
if tt.expectedRun {
|
||||
mockCmd.AssertExpectations(t)
|
||||
}
|
||||
if tt.runSecrets {
|
||||
mockSecrets.AssertExpectations(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -1,14 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"sort"
|
||||
"net/http"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"sort"
|
||||
)
|
||||
func (h *abraHandler) handleGetAppServices (w http.ResponseWriter, r *http.Request, appName string) {
|
||||
|
||||
func (h *abraHandler) handleGetAppServices(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
var tmpMsg map[string]map[string]string
|
||||
cmd := exec.Command("abra", "app", "ps", appName, "-m")
|
||||
output, err := cmd.CombinedOutput()
|
||||
output, err := h.cmd.Run("abra", "app", "ps", appName, "-m")
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
@@ -18,15 +18,15 @@ func (h *abraHandler) handleGetAppServices (w http.ResponseWriter, r *http.Reque
|
||||
return
|
||||
}
|
||||
var services []AbraAppService
|
||||
for _,v := range tmpMsg {
|
||||
for _, v := range tmpMsg {
|
||||
services = append(services, AbraAppService{
|
||||
Service: v["service"],
|
||||
Chaos: v["chaos"] == "true",
|
||||
Chaos: v["chaos"] == "true",
|
||||
Created: v["created"],
|
||||
Image: v["image"],
|
||||
Ports: v["ports"],
|
||||
State: v["state"],
|
||||
Status: v["status"],
|
||||
Image: v["image"],
|
||||
Ports: v["ports"],
|
||||
State: v["state"],
|
||||
Status: v["status"],
|
||||
Version: v["version"],
|
||||
})
|
||||
}
|
||||
@@ -41,4 +41,4 @@ func (h *abraHandler) handleGetAppServices (w http.ResponseWriter, r *http.Reque
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonBytes)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandlePing(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
runOutput []byte
|
||||
runErr error
|
||||
expectedArgs []string
|
||||
expectedBody []AbraAppService
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
runOutput: []byte(`
|
||||
{"app":{
|
||||
"chaos":"false",
|
||||
"created":"3 days ago",
|
||||
"image":"image:4.1.0-22",
|
||||
"ports":"1880/tcp",
|
||||
"service":"app",
|
||||
"state":"running",
|
||||
"status":"Up 3 days (healthy)",
|
||||
"version":"1.0.0+4.1.0-22"}
|
||||
}
|
||||
`),
|
||||
expectedArgs: []string{"app", "ps", "myapp", "-m"},
|
||||
expectedBody: []AbraAppService{{
|
||||
Service: "app",
|
||||
Chaos: false,
|
||||
Created: "3 days ago",
|
||||
Image: "image:4.1.0-22",
|
||||
Ports: "1880/tcp",
|
||||
State: "running",
|
||||
Status: "Up 3 days (healthy)",
|
||||
Version: "1.0.0+4.1.0-22",
|
||||
}},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "success sorted",
|
||||
runOutput: []byte(`
|
||||
{"app":{
|
||||
"chaos":"false",
|
||||
"created":"3 days ago",
|
||||
"image":"image:4.1.0-22",
|
||||
"ports":"1880/tcp",
|
||||
"service":"app",
|
||||
"state":"running",
|
||||
"status":"Up 3 days (healthy)",
|
||||
"version":"1.0.0+4.1.0-22"
|
||||
},
|
||||
"base": {
|
||||
"chaos":"false",
|
||||
"created":"3 days ago",
|
||||
"image":"image:6.1.0",
|
||||
"ports":"1880/tcp",
|
||||
"service":"base",
|
||||
"state":"running",
|
||||
"status":"Up 3 days (healthy)",
|
||||
"version":"1.0.0+6.1.0"
|
||||
}
|
||||
}`),
|
||||
expectedArgs: []string{"app", "ps", "myapp", "-m"},
|
||||
expectedBody: []AbraAppService{{
|
||||
Service: "app",
|
||||
Chaos: false,
|
||||
Created: "3 days ago",
|
||||
Image: "image:4.1.0-22",
|
||||
Ports: "1880/tcp",
|
||||
State: "running",
|
||||
Status: "Up 3 days (healthy)",
|
||||
Version: "1.0.0+4.1.0-22",
|
||||
}, {
|
||||
Service: "base",
|
||||
Chaos: false,
|
||||
Created: "3 days ago",
|
||||
Image: "image:6.1.0",
|
||||
Ports: "1880/tcp",
|
||||
State: "running",
|
||||
Status: "Up 3 days (healthy)",
|
||||
Version: "1.0.0+6.1.0",
|
||||
}},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := new(MockCommandRunner)
|
||||
|
||||
args := []interface{}{"abra"}
|
||||
for _, arg := range tt.expectedArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
mockCmd.
|
||||
On("Run", args...).
|
||||
Return(tt.runOutput, tt.runErr)
|
||||
|
||||
h := &abraHandler{cmd: mockCmd}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleGetAppServices(rec, req, "myapp")
|
||||
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
|
||||
mockCmd.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
+4
-5
@@ -1,15 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"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()
|
||||
output, err := h.cmd.Run("abra", "app", "remove", appName, "-n")
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
InternalServerError(w, fmt.Errorf("Error: %s", string(output)))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
+30
-26
@@ -1,53 +1,57 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os/exec"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"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()
|
||||
func (h *abraHandler) handleGetAppSecrets(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
log.Printf("handling secrets!")
|
||||
secrets, err := h.cmd.Run("abra", "app", "secret", "list", appName, "-m")
|
||||
if err != nil {
|
||||
log.Printf("??")
|
||||
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"`
|
||||
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 {
|
||||
log.Printf("???")
|
||||
InternalServerError(w, err)
|
||||
return
|
||||
}
|
||||
var fs []AppSecret
|
||||
fs := []AppSecret{}
|
||||
for _, secret := range resp {
|
||||
if secret.Created == nil {
|
||||
if secret.Created == nil {
|
||||
InternalServerError(w, errors.New("Failed to decode field 'created on server' from abra response"))
|
||||
return
|
||||
}
|
||||
if secret.Name == nil {
|
||||
if secret.Name == nil {
|
||||
InternalServerError(w, errors.New("Failed to decode field 'name' from abra response"))
|
||||
return
|
||||
}
|
||||
if secret.Version == nil {
|
||||
if secret.Version == nil {
|
||||
InternalServerError(w, errors.New("Failed to decode field 'version' from abra response"))
|
||||
return
|
||||
}
|
||||
fs = append(fs, AppSecret{
|
||||
Name: *secret.Name,
|
||||
Name: *secret.Name,
|
||||
Version: *secret.Version,
|
||||
Created: *secret.Created == "true",
|
||||
Value: "",
|
||||
Value: "",
|
||||
})
|
||||
}
|
||||
log.Printf("%v", fs)
|
||||
jsonBytes, err := json.Marshal(fs)
|
||||
if err != nil {
|
||||
InternalServerError(w, err)
|
||||
@@ -64,34 +68,34 @@ func (h *abraHandler) handleInsertAppSecret(w http.ResponseWriter, r *http.Reque
|
||||
|
||||
// anonymous struct type: handy for one-time use
|
||||
body := struct {
|
||||
Name *string `json:"name"`
|
||||
Version *string `json:"version"`
|
||||
Value *string `json:"value"`
|
||||
Name *string `json:"name"`
|
||||
Version *string `json:"version"`
|
||||
Value *string `json:"value"`
|
||||
}{}
|
||||
|
||||
err := d.Decode(&body)
|
||||
if err != nil {
|
||||
log.Printf("%v", err)
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if body.Name == nil {
|
||||
if body.Name == nil {
|
||||
BadRequest(w, errors.New("missing field 'name' from JSON object"))
|
||||
return
|
||||
}
|
||||
if body.Version == nil {
|
||||
if body.Version == nil {
|
||||
BadRequest(w, errors.New("missing field 'version' from JSON object"))
|
||||
return
|
||||
}
|
||||
if body.Value == nil {
|
||||
if body.Value == nil {
|
||||
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()
|
||||
output, err := h.cmd.Run("abra", "app", "secret", "insert", appName, *body.Name, *body.Version, *body.Value)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
InternalServerError(w, fmt.Errorf("Error: %s", string(output)))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
@@ -0,0 +1,215 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestHandleGetSecrets(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
runOutput []byte
|
||||
runErr error
|
||||
expectedArgs []string
|
||||
expectedCode int
|
||||
expectedBody []AppSecret
|
||||
}{
|
||||
{
|
||||
name: "success no secrets",
|
||||
runOutput: []byte(`[]`),
|
||||
expectedArgs: []string{"app", "secret", "list", "myapp", "-m"},
|
||||
expectedCode: http.StatusOK,
|
||||
expectedBody: []AppSecret{},
|
||||
},
|
||||
{
|
||||
name: "success with secrets",
|
||||
runOutput: []byte(`[
|
||||
{
|
||||
"created on server": "true",
|
||||
"generated name": "myapp_secret",
|
||||
"name": "secret",
|
||||
"version": "v1"
|
||||
},
|
||||
{
|
||||
"created on server": "false",
|
||||
"generated name": "myapp_unknown",
|
||||
"name": "unknown",
|
||||
"version": "v1"
|
||||
}
|
||||
]`),
|
||||
expectedArgs: []string{"app", "secret", "list", "myapp", "-m"},
|
||||
expectedCode: http.StatusOK,
|
||||
expectedBody: []AppSecret{
|
||||
{
|
||||
Name: "secret",
|
||||
Version: "v1",
|
||||
Value: "",
|
||||
Created: true},
|
||||
{
|
||||
Name: "unknown",
|
||||
Version: "v1",
|
||||
Value: "",
|
||||
Created: false},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "failure bad json",
|
||||
runOutput: []byte(`
|
||||
[
|
||||
{
|
||||
"created on server": "true",
|
||||
}
|
||||
]`),
|
||||
expectedArgs: []string{"app", "secret", "list", "myapp", "-m"},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "failure missing fields",
|
||||
runOutput: []byte(`
|
||||
[
|
||||
{
|
||||
"created on server": "true",
|
||||
"generated name": "myapp_fail"
|
||||
}
|
||||
]`),
|
||||
expectedArgs: []string{"app", "secret", "list", "myapp", "-m"},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "failure null field",
|
||||
runOutput: []byte(`
|
||||
[
|
||||
{
|
||||
"created on server": null,
|
||||
"generated name": "myapp_nil",
|
||||
"name": "nil",
|
||||
"version": "v1"
|
||||
}
|
||||
]`),
|
||||
expectedArgs: []string{"app", "secret", "list", "myapp", "-m"},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
{
|
||||
name: "command failure",
|
||||
runOutput: []byte("error"),
|
||||
runErr: errors.New("failed"),
|
||||
expectedArgs: []string{"app", "secret", "list", "myapp", "-m"},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := new(MockCommandRunner)
|
||||
|
||||
args := []interface{}{"abra"}
|
||||
for _, arg := range tt.expectedArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
mockCmd.
|
||||
On("Run", args...).
|
||||
Return(tt.runOutput, tt.runErr)
|
||||
|
||||
h := &abraHandler{cmd: mockCmd}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleGetAppSecrets(rec, req, "myapp")
|
||||
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
if tt.expectedCode == http.StatusOK {
|
||||
var res []AppSecret
|
||||
err := json.Unmarshal(rec.Body.Bytes(), &res)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, tt.expectedBody, res)
|
||||
}
|
||||
|
||||
mockCmd.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleInsert(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reqBody string
|
||||
runOutput []byte
|
||||
runErr error
|
||||
expectedArgs []string
|
||||
expectedRun bool
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
reqBody: `{
|
||||
"name": "myapp_secret",
|
||||
"version": "v1",
|
||||
"value": "secret"
|
||||
}`,
|
||||
runOutput: []byte(nil),
|
||||
expectedArgs: []string{"app", "secret", "insert", "myapp", "myapp_secret", "v1", "secret"},
|
||||
expectedRun: true,
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "failure missing fields",
|
||||
reqBody: `{
|
||||
"name": "myapp_secret",
|
||||
}`,
|
||||
runOutput: []byte(nil),
|
||||
expectedArgs: []string{"app", "secret", "insert", "myapp", "myapp_secret", "v1", "secret"},
|
||||
expectedRun: false,
|
||||
expectedCode: http.StatusBadRequest,
|
||||
},
|
||||
{
|
||||
name: "failure abra",
|
||||
reqBody: `{
|
||||
"name": "myapp_secret",
|
||||
"version": "v1",
|
||||
"value": "secret"
|
||||
}`,
|
||||
runOutput: []byte(nil),
|
||||
runErr: errors.New("error"),
|
||||
expectedArgs: []string{"app", "secret", "insert", "myapp", "myapp_secret", "v1", "secret"},
|
||||
expectedRun: true,
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := new(MockCommandRunner)
|
||||
|
||||
args := []interface{}{"abra"}
|
||||
for _, arg := range tt.expectedArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
mockCmd.
|
||||
On("Run", args...).
|
||||
Return(tt.runOutput, tt.runErr)
|
||||
|
||||
h := &abraHandler{cmd: mockCmd}
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tt.reqBody))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleInsertAppSecret(rec, req, "myapp")
|
||||
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
|
||||
if tt.expectedRun {
|
||||
mockCmd.AssertExpectations(t)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
package cli
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
func (h *abraHandler) handleUndeployApp(w http.ResponseWriter, r *http.Request, appName string) {
|
||||
output, err := h.cmd.Run("abra", "app", "undeploy", appName, "-n")
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
+16
-12
@@ -1,13 +1,14 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"log"
|
||||
"fmt"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (h* abraHandler) handleUpgradeApp(w http.ResponseWriter, r *http.Request, appName string, force bool) {
|
||||
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"}
|
||||
@@ -23,14 +24,14 @@ func (h* abraHandler) handleUpgradeApp(w http.ResponseWriter, r *http.Request, a
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
func (h* abraHandler) handleRollbackApp(w http.ResponseWriter, r *http.Request, appName string, force bool) {
|
||||
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()
|
||||
|
||||
d.DisallowUnknownFields()
|
||||
|
||||
body := struct {
|
||||
Version *string `json:"version"`
|
||||
Version *string `json:"version"`
|
||||
}{}
|
||||
err := d.Decode(&body)
|
||||
if err != nil {
|
||||
@@ -38,13 +39,16 @@ func (h* abraHandler) handleRollbackApp(w http.ResponseWriter, r *http.Request,
|
||||
BadRequest(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if body.Version == nil {
|
||||
BadRequest(w, fmt.Errorf("Error: no version for rollback"))
|
||||
return
|
||||
}
|
||||
|
||||
args := []string{"app", "rollback", appName, "-n", "-c"}
|
||||
if force {
|
||||
args = append(args, "-f")
|
||||
}
|
||||
if body.Version != nil{
|
||||
if body.Version != nil {
|
||||
if *body.Version == "Auto" {
|
||||
log.Printf("leaving abra to determine rollback version")
|
||||
} else {
|
||||
@@ -54,9 +58,9 @@ func (h* abraHandler) handleRollbackApp(w http.ResponseWriter, r *http.Request,
|
||||
|
||||
output, err := h.cmd.Run("abra", args...)
|
||||
if err != nil {
|
||||
InternalServerError(w, errors.New(fmt.Sprintf("Error: %s", string(output))))
|
||||
InternalServerError(w, fmt.Errorf("Error: %s", string(output)))
|
||||
return
|
||||
}
|
||||
log.Printf("Finishing app rollback!")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
}
|
||||
|
||||
+140
-89
@@ -1,117 +1,168 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
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 string
|
||||
force bool
|
||||
runOutput []byte
|
||||
runErr error
|
||||
expectedArgs []string
|
||||
expectedCode int
|
||||
}{
|
||||
{
|
||||
name: "success",
|
||||
appName: "myapp",
|
||||
force: false,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArgs: []string{
|
||||
"app",
|
||||
"upgrade",
|
||||
"myapp",
|
||||
"-n",
|
||||
"-c",
|
||||
},
|
||||
name: "success",
|
||||
force: false,
|
||||
runOutput: []byte("ok"),
|
||||
expectedArgs: []string{"app", "upgrade", "myapp", "-n", "-c"},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "force success",
|
||||
appName: "myapp",
|
||||
force: true,
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedArgs: []string{
|
||||
"app",
|
||||
"upgrade",
|
||||
"myapp",
|
||||
"-n",
|
||||
"-c",
|
||||
"-f",
|
||||
},
|
||||
name: "success with force",
|
||||
force: true,
|
||||
runOutput: []byte("ok"),
|
||||
expectedArgs: []string{"app", "upgrade", "myapp", "-n", "-c", "-f"},
|
||||
expectedCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "command failure",
|
||||
appName: "myapp",
|
||||
force: false,
|
||||
mockErr: errors.New("dead"),
|
||||
expectedStatus: http.StatusInternalServerError,
|
||||
expectedArgs: []string{
|
||||
"app",
|
||||
"upgrade",
|
||||
"myapp",
|
||||
"-n",
|
||||
"-c",
|
||||
},
|
||||
name: "command failure",
|
||||
force: false,
|
||||
runOutput: []byte("error"),
|
||||
runErr: errors.New("failed"),
|
||||
expectedArgs: []string{"app", "upgrade", "myapp", "-n", "-c"},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := &MockRunner{
|
||||
Err: tt.mockErr,
|
||||
mockCmd := new(MockCommandRunner)
|
||||
|
||||
args := []interface{}{"abra"}
|
||||
for _, arg := range tt.expectedArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
|
||||
h := &abraHandler{
|
||||
cmd: mockCmd,
|
||||
}
|
||||
mockCmd.
|
||||
On("Run", args...).
|
||||
Return(tt.runOutput, tt.runErr)
|
||||
|
||||
h := &abraHandler{cmd: mockCmd}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", nil)
|
||||
w := httptest.NewRecorder()
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleUpgradeApp(w, req, tt.appName, tt.force)
|
||||
h.handleUpgradeApp(rec, req, "myapp", tt.force)
|
||||
|
||||
res := w.Result()
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
mockCmd.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleRollbackApp(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
force bool
|
||||
runOutput []byte
|
||||
runErr error
|
||||
expectedArgs []string
|
||||
expectedCode int
|
||||
expectedRun bool
|
||||
}{
|
||||
{
|
||||
name: "success with auto version no force",
|
||||
body: `{
|
||||
"version": "Auto"
|
||||
}`,
|
||||
force: false,
|
||||
runOutput: []byte("ok"),
|
||||
expectedArgs: []string{"app", "rollback", "myapp", "-n", "-c"},
|
||||
expectedCode: http.StatusOK,
|
||||
expectedRun: true,
|
||||
},
|
||||
{
|
||||
name: "success with auto version with force",
|
||||
body: `{
|
||||
"version": "Auto"
|
||||
}`,
|
||||
force: true,
|
||||
runOutput: []byte("ok"),
|
||||
expectedArgs: []string{"app", "rollback", "myapp", "-n", "-c", "-f"},
|
||||
expectedCode: http.StatusOK,
|
||||
expectedRun: true,
|
||||
},
|
||||
{
|
||||
name: "failure no body",
|
||||
body: `{
|
||||
}`,
|
||||
force: false,
|
||||
runOutput: []byte("ok"),
|
||||
expectedArgs: []string{"app", "rollback", "myapp", "-n", "-c"},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedRun: false,
|
||||
},
|
||||
{
|
||||
name: "failure unknown field",
|
||||
body: `{
|
||||
"unknown": "unknown"
|
||||
}`,
|
||||
force: false,
|
||||
expectedArgs: []string{"app", "rollback", "myapp", "-n", "-c"},
|
||||
expectedCode: http.StatusBadRequest,
|
||||
expectedRun: false,
|
||||
},
|
||||
{
|
||||
name: "failure abra error",
|
||||
body: `{
|
||||
"version": "1.0.0"
|
||||
}`,
|
||||
force: false,
|
||||
runOutput: []byte("error"),
|
||||
runErr: errors.New("failed"),
|
||||
expectedArgs: []string{"app", "rollback", "myapp", "-n", "-c", "1.0.0"},
|
||||
expectedCode: http.StatusInternalServerError,
|
||||
expectedRun: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mockCmd := new(MockCommandRunner)
|
||||
|
||||
args := []interface{}{"abra"}
|
||||
for _, arg := range tt.expectedArgs {
|
||||
args = append(args, arg)
|
||||
}
|
||||
if tt.expectedRun {
|
||||
mockCmd.
|
||||
On("Run", args...).
|
||||
Return(tt.runOutput, tt.runErr).
|
||||
Once()
|
||||
}
|
||||
|
||||
h := &abraHandler{cmd: mockCmd}
|
||||
|
||||
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(tt.body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rec := httptest.NewRecorder()
|
||||
|
||||
h.handleRollbackApp(rec, req, "myapp", tt.force)
|
||||
|
||||
assert.Equal(t, tt.expectedCode, rec.Code)
|
||||
|
||||
mockCmd.AssertExpectations(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"coopcloud.tech/abra/pkg/app"
|
||||
"coopcloud.tech/abra/pkg/envfile"
|
||||
"coopcloud.tech/abra/pkg/recipe"
|
||||
)
|
||||
|
||||
type AppModifier func(*app.App)
|
||||
|
||||
// func App() app.App {
|
||||
// return app.App{
|
||||
// Name: "nextcloud",
|
||||
// Domain: "cloud.example.com",
|
||||
// Server: "server.example.com",
|
||||
// Path: "/home/test/.abra/servers/hetzner/cloud.example.com.env",
|
||||
// Env: envfile.AppEnv{
|
||||
// "DOMAIN": "cloud.example.com",
|
||||
// "LETS_ENCRYPT_ENABLED": "1",
|
||||
// },
|
||||
// Recipe: recipe.Recipe{
|
||||
// Name: "nextcloud",
|
||||
// },
|
||||
// }
|
||||
// }
|
||||
func App(modifiers ...AppModifier) app.App {
|
||||
a := app.App{
|
||||
Name: "nextcloud.example.com",
|
||||
Domain: "cloud.example.com",
|
||||
Server: "example.com",
|
||||
Path: "/home/test/.abra/servers/hetzner/cloud.example.com.env",
|
||||
Env: envfile.AppEnv{
|
||||
"DOMAIN": "cloud.example.com",
|
||||
"LETS_ENCRYPT_ENABLED": "1",
|
||||
},
|
||||
Recipe: recipe.Recipe{
|
||||
Name: "nextcloud",
|
||||
},
|
||||
}
|
||||
for _, mod := range modifiers {
|
||||
mod(&a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func RecipeName(name string) AppModifier {
|
||||
return func(a *app.App) {
|
||||
domainName := fmt.Sprintf("%s.%s", name, a.Server)
|
||||
a.Name = name
|
||||
a.Domain = domainName
|
||||
a.Recipe.Name = name
|
||||
a.Env["DOMAIN"] = domainName
|
||||
}
|
||||
}
|
||||
|
||||
func Broken() AppModifier {
|
||||
return func(a *app.App) {
|
||||
delete(a.Env, "DOMAIN")
|
||||
}
|
||||
}
|
||||
func WithServer(server string) AppModifier {
|
||||
return func(a *app.App) {
|
||||
a.Server = server
|
||||
}
|
||||
}
|
||||
|
||||
func WithDomain(domain string) AppModifier {
|
||||
return func(a *app.App) {
|
||||
a.Domain = domain
|
||||
a.Env["DOMAIN"] = domain
|
||||
}
|
||||
}
|
||||
|
||||
// version, chaosVersion, chaos, status
|
||||
type AppStatusModifier func(map[string]map[string]string)
|
||||
|
||||
func AppStatus(modifiers ...AppStatusModifier) map[string]map[string]string {
|
||||
a := map[string]map[string]string{
|
||||
"nextcloud": {
|
||||
"version": "1.0.0",
|
||||
"chaos": "false",
|
||||
"chaosVersion": "unknown",
|
||||
"status": "running",
|
||||
},
|
||||
}
|
||||
for _, mod := range modifiers {
|
||||
mod(a)
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func WithChaos(app string) AppStatusModifier {
|
||||
return func(a map[string]map[string]string) {
|
||||
a[app]["chaos"] = "true"
|
||||
a[app]["chaosVersion"] = "version"
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,9 @@ require (
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/containers/image v3.0.2+incompatible // indirect
|
||||
github.com/cpuguy83/dockercfg v0.3.2 // indirect
|
||||
github.com/cyphar/filepath-securejoin v0.5.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decentral1se/passgen v1.0.1 // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/distribution v2.8.3+incompatible // indirect
|
||||
@@ -48,6 +50,7 @@ require (
|
||||
github.com/docker/go-metrics v0.0.1 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/emirpasic/gods v1.18.1 // indirect
|
||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -59,6 +62,7 @@ require (
|
||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||
@@ -72,9 +76,12 @@ require (
|
||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/leonelquinteros/gotext v1.7.2 // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
||||
github.com/magiconair/properties v1.8.10 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
||||
@@ -83,9 +90,16 @@ require (
|
||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/go-archive v0.2.0 // indirect
|
||||
github.com/moby/moby/api v1.54.2 // indirect
|
||||
github.com/moby/moby/client v0.4.0 // indirect
|
||||
github.com/moby/patternmatcher v0.6.1 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/sys/signal v0.7.1 // indirect
|
||||
github.com/moby/sys/user v0.4.0 // indirect
|
||||
github.com/moby/sys/userns v0.1.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||
@@ -97,6 +111,8 @@ require (
|
||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
||||
github.com/prometheus/client_model v0.6.2 // indirect
|
||||
github.com/prometheus/common v0.66.1 // indirect
|
||||
@@ -105,36 +121,43 @@ require (
|
||||
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
|
||||
github.com/shirou/gopsutil/v4 v4.26.5 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/skeema/knownhosts v1.3.1 // indirect
|
||||
github.com/spf13/cobra v1.10.1 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect
|
||||
github.com/testcontainers/testcontainers-go v0.43.0 // indirect
|
||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
|
||||
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
|
||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.41.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.41.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/crypto v0.51.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sys v0.36.0 // indirect
|
||||
golang.org/x/term v0.35.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/net v0.53.0 // indirect
|
||||
golang.org/x/sys v0.45.0 // indirect
|
||||
golang.org/x/term v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
||||
|
||||
@@ -116,6 +116,8 @@ github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc
|
||||
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA=
|
||||
github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||
@@ -167,6 +169,8 @@ github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDD
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU5CAUmr9zpesgbU6SWc8/B4mflAE4=
|
||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
|
||||
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||
@@ -211,6 +215,8 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE=
|
||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
@@ -242,6 +248,7 @@ github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519
|
||||
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
@@ -301,6 +308,8 @@ github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7Dmvb
|
||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
@@ -320,9 +329,13 @@ github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/magiconair/properties v1.5.3 h1:C8fxWnhYyME3n0klPOhVM7PtYUB3eV1W3DeFmN3j53Y=
|
||||
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE=
|
||||
github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@@ -355,14 +368,29 @@ github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyua
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/go-archive v0.2.0 h1:zg5QDUM2mi0JIM9fdQZWC7U8+2ZfixfTYoHL7rWUcP8=
|
||||
github.com/moby/go-archive v0.2.0/go.mod h1:mNeivT14o8xU+5q1YnNrkQVpK+dnNe/K6fHqnTg4qPU=
|
||||
github.com/moby/moby/api v1.54.2 h1:wiat9QAhnDQjA7wk1kh/TqHz2I1uUA7M7t9SAl/JNXg=
|
||||
github.com/moby/moby/api v1.54.2/go.mod h1:+RQ6wluLwtYaTd1WnPLykIDPekkuyD/ROWQClE83pzs=
|
||||
github.com/moby/moby/client v0.4.0 h1:S+2XegzHQrrvTCvF6s5HFzcrywWQmuVnhOXe2kiWjIw=
|
||||
github.com/moby/moby/client v0.4.0/go.mod h1:QWPbvWchQbxBNdaLSpoKpCdf5E+WxFAgNHogCWDoa7g=
|
||||
github.com/moby/patternmatcher v0.6.1 h1:qlhtafmr6kgMIJjKJMDmMWq7WLkKIo23hsrpR3x084U=
|
||||
github.com/moby/patternmatcher v0.6.1/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
||||
github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc=
|
||||
github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
@@ -418,6 +446,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prometheus/client_golang v0.9.0-pre1.0.20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
|
||||
@@ -472,6 +502,8 @@ github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN
|
||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||
github.com/shirou/gopsutil/v4 v4.26.5 h1:RPcBXkpz7kOj9PqGFQOlBPZHsyaPvPVQc098y9RmCNM=
|
||||
github.com/shirou/gopsutil/v4 v4.26.5/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
@@ -479,6 +511,8 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.4 h1:TsZE7l11zFCLZnZ+teH4Umoq5BhEIfIzfRDZ1Uzql2w=
|
||||
github.com/sirupsen/logrus v1.9.4/go.mod h1:ftWc9WdOfJ0a92nsE2jF5u5ZwH8Bv2zdeOC42RjbV2g=
|
||||
github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A=
|
||||
github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
|
||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
||||
@@ -511,6 +545,10 @@ github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/y
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4=
|
||||
github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
@@ -520,10 +558,17 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||
github.com/testcontainers/testcontainers-go v0.43.0 h1:oEQx5MW2DGd9z3AeEQfB2lPM0eLs7ztyaGRu75bFo5A=
|
||||
github.com/testcontainers/testcontainers-go v0.43.0/go.mod h1:+VxkT2NQnKOZPKi6praMuMKYHYyOGXr0XSBSlSMCzFo=
|
||||
github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c=
|
||||
github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||
@@ -542,6 +587,8 @@ github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||
@@ -553,6 +600,8 @@ go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
|
||||
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel v1.41.0 h1:YlEwVsGAlCvczDILpUXpIpPSL/VPugt7zHThEMLce1c=
|
||||
go.opentelemetry.io/otel v1.41.0/go.mod h1:Yt4UwgEKeT05QbLwbyHXEwhnjxNO6D8L5PQP51/46dE=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 h1:U2guen0GhqH8o/G2un8f/aG/y++OuW6MyCo6hT9prXk=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0/go.mod h1:yeGZANgEcpdx/WK0IvvRFC+2oLiMS2u4L/0Rj2M2Qr0=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
||||
@@ -571,6 +620,8 @@ go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6b
|
||||
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
|
||||
go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
||||
go.opentelemetry.io/otel/metric v1.41.0 h1:rFnDcs4gRzBcsO9tS8LCpgR0dxg4aaxWlJxCno7JlTQ=
|
||||
go.opentelemetry.io/otel/metric v1.41.0/go.mod h1:xPvCwd9pU0VN8tPZYzDZV/BMj9CM9vs00GuBjeKhJps=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
|
||||
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
||||
@@ -583,6 +634,8 @@ go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+
|
||||
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.opentelemetry.io/otel/trace v1.41.0 h1:Vbk2co6bhj8L59ZJ6/xFTskY+tGAbOnCtQGVVa9TIN0=
|
||||
go.opentelemetry.io/otel/trace v1.41.0/go.mod h1:U1NU4ULCoxeDKc09yCWdWe+3QoyweJcISEVa1RBzOis=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||
go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
||||
@@ -609,6 +662,8 @@ golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30=
|
||||
golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M=
|
||||
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8 h1:yixxcjnhBmY0nkL253HFVIm0JsFHwrHdT3Yh6szTnfY=
|
||||
golang.org/x/exp v0.0.0-20240613232115-7f521ea00fb8/go.mod h1:jj3sYF3dwk5D+ghuXyeI3r5MFf+NT2An6/9dOA95KSI=
|
||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
||||
@@ -640,6 +695,8 @@ golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
||||
golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA=
|
||||
golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@@ -658,10 +715,12 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
|
||||
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -680,6 +739,8 @@ golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
|
||||
golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
@@ -691,6 +752,8 @@ golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk=
|
||||
golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4=
|
||||
golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
||||
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -702,6 +765,8 @@ golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
|
||||
golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
|
||||
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
|
||||
Reference in New Issue
Block a user