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:
AndReicscs
2026-06-06 09:11:22 +00:00
parent 57ed8deb77
commit e00967f708
+53 -54
View File
@@ -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 {