mirror of
https://github.com/andreicscs/HoneyWire
synced 2026-06-26 12:39:53 +00:00
removed --wait flasg on compose up, didn't catch port binding conflicts, implemented docker ps check for succesfull container startup, rollback on failure.
This commit is contained in:
@@ -8,10 +8,46 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
"strings"
|
||||
|
||||
"github.com/honeywire/wizard/internal/cli"
|
||||
)
|
||||
|
||||
func performRollback(reason string, hasBackup bool, backupPath, composePath string, cmdBase []string) error {
|
||||
fmt.Printf("\n%s[!] %s. Attempting rollback...%s\n", cli.Yellow, reason, cli.Reset)
|
||||
|
||||
if hasBackup {
|
||||
if rollbackErr := copyFile(backupPath, composePath); rollbackErr != nil {
|
||||
fmt.Printf("%s[!] Rollback failed to restore previous compose: %v%s\n", cli.Red, rollbackErr, cli.Reset)
|
||||
fmt.Printf(" Manual recovery may be required at: %s\n", composePath)
|
||||
} else {
|
||||
rollbackCtx, rollbackCancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer rollbackCancel()
|
||||
|
||||
rollbackArgs := append(cmdBase, "-f", ComposeFile, "-p", ProjectName, "up", "-d", "--remove-orphans")
|
||||
rollbackCmd := exec.CommandContext(rollbackCtx, rollbackArgs[0], rollbackArgs[1:]...)
|
||||
rollbackCmd.Dir = DeployDir
|
||||
rollbackCmd.Stdout = os.Stdout
|
||||
rollbackCmd.Stderr = os.Stderr
|
||||
|
||||
if rollbackErr := rollbackCmd.Run(); rollbackErr != nil {
|
||||
fmt.Printf("%s[!] Rollback compose up failed: %v%s\n", cli.Red, rollbackErr, cli.Reset)
|
||||
fmt.Printf(" Manual recovery may be required at: %s\n", composePath)
|
||||
} else {
|
||||
fmt.Printf("%s[*] Rolled back to previous compose configuration.%s\n", cli.Yellow, cli.Reset)
|
||||
os.Remove(backupPath)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%s[!] No backup available for rollback.%s\n", cli.Red, cli.Reset)
|
||||
if removeErr := os.Remove(composePath); removeErr != nil {
|
||||
fmt.Printf("%s[!] Warning: failed to remove failed compose file: %v%s\n", cli.Yellow, removeErr, cli.Reset)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("deployment failed: %s", reason)
|
||||
}
|
||||
|
||||
func Apply(ctx context.Context, composeData []byte) error {
|
||||
cmdBase, err := GetDockerCommand()
|
||||
if err != nil {
|
||||
@@ -28,21 +64,6 @@ func Apply(ctx context.Context, composeData []byte) error {
|
||||
return fmt.Errorf("failed to write new compose file: %w", err)
|
||||
}
|
||||
|
||||
validateCtx, validateCancel := context.WithTimeout(context.Background(), commandTimeout)
|
||||
defer validateCancel()
|
||||
|
||||
validateArgs := append(cmdBase, "-f", newComposePath, "config", "--quiet")
|
||||
|
||||
// codeql[go/command-injection] Hardcoded/trusted CLI arguments.
|
||||
// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command
|
||||
validateCmd := exec.CommandContext(validateCtx, validateArgs[0], validateArgs[1:]...)
|
||||
validateCmd.Dir = DeployDir
|
||||
|
||||
if output, err := validateCmd.CombinedOutput(); err != nil {
|
||||
os.Remove(newComposePath)
|
||||
return fmt.Errorf("compose validation failed: %s\nOutput: %s", err, string(output))
|
||||
}
|
||||
|
||||
backupPath := composePath + ".backup.yml"
|
||||
hasBackup := false
|
||||
if _, err := os.Stat(composePath); err == nil {
|
||||
@@ -58,16 +79,12 @@ func Apply(ctx context.Context, composeData []byte) error {
|
||||
return fmt.Errorf("failed to swap compose file: %w", err)
|
||||
}
|
||||
|
||||
// Explicitly pull latest images before starting
|
||||
fmt.Printf("%s[*] Pulling latest container images...%s\n", cli.Cyan, cli.Reset)
|
||||
|
||||
pullCtx, pullCancel := context.WithTimeout(context.Background(), 10*time.Minute)
|
||||
defer pullCancel()
|
||||
|
||||
pullArgs := append(cmdBase, "-f", ComposeFile, "-p", ProjectName, "pull")
|
||||
|
||||
// codeql[go/command-injection] Hardcoded/trusted CLI arguments.
|
||||
// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command
|
||||
pullCmd := exec.CommandContext(pullCtx, pullArgs[0], pullArgs[1:]...)
|
||||
pullCmd.Dir = DeployDir
|
||||
pullCmd.Stdout = os.Stdout
|
||||
@@ -80,51 +97,32 @@ func Apply(ctx context.Context, composeData []byte) error {
|
||||
upCtx, upCancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer upCancel()
|
||||
|
||||
upArgs := append(cmdBase, "-f", ComposeFile, "-p", ProjectName, "up", "-d", "--wait", "--pull", "always", "--remove-orphans")
|
||||
|
||||
// codeql[go/command-injection] Hardcoded/trusted CLI arguments.
|
||||
// nosemgrep: go.lang.security.audit.dangerous-exec-command.dangerous-exec-command
|
||||
upArgs := append(cmdBase, "-f", ComposeFile, "-p", ProjectName, "up", "-d", "--pull", "always", "--remove-orphans")
|
||||
upCmd := exec.CommandContext(upCtx, upArgs[0], upArgs[1:]...)
|
||||
upCmd.Dir = DeployDir
|
||||
upCmd.Stdout = os.Stdout
|
||||
upCmd.Stderr = os.Stderr
|
||||
|
||||
if err := upCmd.Run(); err != nil {
|
||||
fmt.Printf("\n%s[!] Compose up failed. Attempting rollback...%s\n", cli.Yellow, cli.Reset)
|
||||
return performRollback(fmt.Sprintf("Compose up failed: %v", err), hasBackup, backupPath, composePath, cmdBase)
|
||||
}
|
||||
|
||||
if hasBackup {
|
||||
if rollbackErr := copyFile(backupPath, composePath); rollbackErr != nil {
|
||||
fmt.Printf("%s[!] Rollback failed to restore previous compose: %v%s\n", cli.Red, rollbackErr, cli.Reset)
|
||||
fmt.Printf(" Manual recovery may be required at: %s\n", composePath)
|
||||
} else {
|
||||
rollbackCtx, rollbackCancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer rollbackCancel()
|
||||
fmt.Printf("%s[*] Verifying deployment success...%s\n", cli.Cyan, cli.Reset)
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
rollbackArgs := append(cmdBase, "-f", ComposeFile, "-p", ProjectName, "up", "-d", "--remove-orphans")
|
||||
rollbackCmd := exec.CommandContext(rollbackCtx, rollbackArgs[0], rollbackArgs[1:]...)
|
||||
rollbackCmd.Dir = DeployDir
|
||||
rollbackCmd.Stdout = os.Stdout
|
||||
rollbackCmd.Stderr = os.Stderr
|
||||
checkCtx, checkCancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer checkCancel()
|
||||
|
||||
if rollbackErr := rollbackCmd.Run(); rollbackErr != nil {
|
||||
fmt.Printf("%s[!] Rollback compose up failed: %v%s\n", cli.Red, rollbackErr, cli.Reset)
|
||||
fmt.Printf(" Manual recovery may be required at: %s\n", composePath)
|
||||
} else {
|
||||
fmt.Printf("%s[*] Rolled back to previous compose configuration.%s\n", cli.Yellow, cli.Reset)
|
||||
ofBackupErr := os.Remove(backupPath)
|
||||
if ofBackupErr != nil {
|
||||
fmt.Printf("%s[!] Warning: failed to remove backup file: %v%s\n", cli.Yellow, ofBackupErr, cli.Reset)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf("%s[!] No backup available for rollback.%s\n", cli.Red, cli.Reset)
|
||||
if removeErr := os.Remove(composePath); removeErr != nil {
|
||||
fmt.Printf("%s[!] Warning: failed to remove failed compose file: %v%s\n", cli.Yellow, removeErr, cli.Reset)
|
||||
}
|
||||
}
|
||||
checkArgs := append(cmdBase, "-f", ComposeFile, "-p", ProjectName, "ps", "--services", "--filter", "status=exited", "--filter", "status=restarting")
|
||||
checkCmd := exec.CommandContext(checkCtx, checkArgs[0], checkArgs[1:]...)
|
||||
checkCmd.Dir = DeployDir
|
||||
|
||||
return fmt.Errorf("docker compose up failed: %w", err)
|
||||
output, _ := checkCmd.Output()
|
||||
failedServices := strings.TrimSpace(string(output))
|
||||
|
||||
if failedServices != "" {
|
||||
failedList := strings.ReplaceAll(failedServices, "\n", ", ")
|
||||
return performRollback(fmt.Sprintf("Sensors crashed after startup: %s", failedList), hasBackup, backupPath, composePath, cmdBase)
|
||||
}
|
||||
|
||||
if hasBackup {
|
||||
@@ -134,6 +132,7 @@ func Apply(ctx context.Context, composeData []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func copyFile(src, dst string) error {
|
||||
in, err := os.Open(src)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user