Skip to content

Commit c8008cd

Browse files
feat: improve Consul installation UX with better progress messages and confirmation flow
1 parent 81ba902 commit c8008cd

2 files changed

Lines changed: 44 additions & 21 deletions

File tree

cmd/create/consul.go

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package create
55
import (
66
"fmt"
77
"os"
8+
"strings"
89

910
"github.com/CodeMonkeyCybersecurity/eos/pkg/consul"
1011
"github.com/CodeMonkeyCybersecurity/eos/pkg/eos_cli"
@@ -18,7 +19,7 @@ import (
1819

1920
var CreateConsulCmd = &cobra.Command{
2021
Use: "consul",
21-
Short: "Install and configure HashiCorp Consul directly (without )",
22+
Short: "Install and configure HashiCorp Consul using native methods",
2223
Long: `Install and configure HashiCorp Consul using native installation methods.
2324
2425
This command installs Consul directly without using configuration management tools,
@@ -101,11 +102,17 @@ func runCreateConsul(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []strin
101102
logger.Info("Defaulting to server mode (neither --server nor --client specified)")
102103
}
103104

104-
// Warn about destructive operations
105+
// Warn about destructive operations with explicit confirmation
105106
if consulClean {
106107
logger.Warn("--clean flag specified: This will DELETE all existing Consul data")
107-
logger.Info("terminal prompt: Press Enter to continue or Ctrl+C to cancel...")
108-
fmt.Scanln() // Wait for user confirmation
108+
logger.Info("terminal prompt: Type 'yes' to confirm or Ctrl+C to cancel: ")
109+
110+
var confirmation string
111+
fmt.Scanln(&confirmation)
112+
if strings.ToLower(strings.TrimSpace(confirmation)) != "yes" {
113+
return eos_err.NewUserError("clean install cancelled by user (did not type 'yes')")
114+
}
115+
logger.Info("Clean install confirmed")
109116
}
110117

111118
logger.Info("Starting native Consul installation",
@@ -144,13 +151,17 @@ func runCreateConsul(rc *eos_io.RuntimeContext, cmd *cobra.Command, args []strin
144151
return fmt.Errorf("consul installation failed: %w", err)
145152
}
146153

147-
logger.Info("terminal prompt: Consul installation completed successfully!")
148-
logger.Info(fmt.Sprintf("terminal prompt: Web UI available at: http://<server-ip>:%d", shared.PortConsul))
154+
// CRITICAL: Success message is printed AFTER Install() returns successfully
155+
// Install() includes verification, so if we reach here, Consul is actually working
149156
logger.Info("terminal prompt: ")
150-
logger.Info("terminal prompt: Next steps:")
151-
logger.Info("terminal prompt: 1. Check status: consul members")
152-
logger.Info("terminal prompt: 2. View logs: journalctl -u consul -f")
153-
logger.Info(fmt.Sprintf("terminal prompt: 3. Access UI: http://localhost:%d/ui", shared.PortConsul))
157+
logger.Info("terminal prompt: ✓ Consul installation completed successfully!")
158+
logger.Info("terminal prompt: ")
159+
logger.Info(fmt.Sprintf("terminal prompt: Web UI: http://<server-ip>:%d/ui", shared.PortConsul))
160+
logger.Info("terminal prompt: ")
161+
logger.Info("terminal prompt: Quick Start:")
162+
logger.Info("terminal prompt: consul members # View cluster members")
163+
logger.Info("terminal prompt: journalctl -u consul -f # View live logs")
164+
logger.Info(fmt.Sprintf("terminal prompt: curl http://localhost:%d/v1/agent/self # Test API", shared.PortConsul))
154165

155166
return nil
156167
}

pkg/consul/install.go

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func (ci *ConsulInstaller) Install() error {
145145
}()
146146

147147
// Phase 1: ASSESS - Check if already installed
148-
ci.progress.Update("[16%] Checking current Consul status")
148+
ci.progress.Update("[16%] Checking if Consul is already installed and running...")
149149
shouldInstall, err := ci.assess()
150150
if err != nil {
151151
return fmt.Errorf("assessment failed: %w", err)
@@ -160,34 +160,38 @@ func (ci *ConsulInstaller) Install() error {
160160
}
161161

162162
// Phase 2: Prerequisites
163-
ci.progress.Update("[33%] Validating prerequisites")
163+
ci.progress.Update("[33%] Checking system requirements (memory, disk, ports)...")
164164
if err := ci.validatePrerequisites(); err != nil {
165165
return fmt.Errorf("prerequisite validation failed: %w", err)
166166
}
167167

168168
// Phase 3: INTERVENE - Install
169-
ci.progress.Update("[50%] Installing Consul binary")
169+
if ci.config.UseRepository {
170+
ci.progress.Update("[50%] Downloading and installing Consul from HashiCorp repository...")
171+
} else {
172+
ci.progress.Update("[50%] Downloading and installing Consul binary...")
173+
}
170174
if err := ci.installBinary(); err != nil {
171175
return fmt.Errorf("binary installation failed: %w", err)
172176
}
173177
binaryInstalled = true
174178

175179
// Phase 4: Configure
176-
ci.progress.Update("[66%] Configuring Consul")
180+
ci.progress.Update("[66%] Generating and validating Consul configuration files...")
177181
if err := ci.configure(); err != nil {
178182
return fmt.Errorf("configuration failed: %w", err)
179183
}
180184
configCreated = true
181185

182186
// Phase 5: Setup Service
183-
ci.progress.Update("[83%] Setting up systemd service")
187+
ci.progress.Update("[83%] Creating systemd service and starting Consul...")
184188
if err := ci.setupService(); err != nil {
185189
return fmt.Errorf("service setup failed: %w", err)
186190
}
187191
serviceCreated = true
188192

189193
// Phase 6: EVALUATE - Verify
190-
ci.progress.Update("[100%] Verifying installation")
194+
ci.progress.Update("[100%] Waiting for Consul API to become ready...")
191195
if err := ci.verify(); err != nil {
192196
return fmt.Errorf("verification failed: %w", err)
193197
}
@@ -243,10 +247,17 @@ func (ci *ConsulInstaller) rollbackPartialInstall(binaryInstalled, configCreated
243247
zap.Error(err),
244248
zap.String("output", output))
245249
} else {
246-
// CRITICAL: daemon-reload is async - systemd scans /etc/systemd/system/
247-
// If we immediately delete files, systemd might see partial state
250+
// CRITICAL: daemon-reload is async - poll systemd state instead of sleep
248251
ci.logger.Debug("Waiting for daemon-reload to complete")
249-
time.Sleep(500 * time.Millisecond)
252+
deadline := time.Now().Add(2 * time.Second)
253+
for time.Now().Before(deadline) {
254+
// Check if systemd has processed the reload by querying unit state
255+
if _, err := exec.Command("systemctl", "show", "-p", "LoadState", "consul").Output(); err == nil {
256+
// If we can query the unit state, daemon-reload has processed
257+
break
258+
}
259+
time.Sleep(100 * time.Millisecond)
260+
}
250261
}
251262
}
252263

@@ -897,9 +908,10 @@ func (ci *ConsulInstaller) setupService() error {
897908

898909
// Log progress every few attempts so user knows we're still checking
899910
if attempt%3 == 0 {
900-
ci.logger.Info("Still waiting for Consul to be ready",
911+
ci.logger.Info("Waiting for Consul HTTP API to respond...",
901912
zap.Int("attempt", attempt),
902-
zap.Int("max_attempts", 15))
913+
zap.Int("max_attempts", 15),
914+
zap.String("checking", fmt.Sprintf("http://127.0.0.1:%d/v1/agent/self", shared.PortConsul)))
903915
}
904916
}
905917
}

0 commit comments

Comments
 (0)