Skip to content

Commit edd9b8e

Browse files
committed
Fifth Refactor CmdCreateBastion
1 parent c8b5139 commit edd9b8e

File tree

4 files changed

+1046
-42
lines changed

4 files changed

+1046
-42
lines changed

CmdCreateBastion.go

Lines changed: 96 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ type sshConfig struct {
6161
RetryDelay time.Duration
6262
}
6363

64-
// newSSHConfig creates SSH configuration with defaults
64+
// newSSHConfig creates SSH configuration with defaults.
65+
// It sets the user to "cloud-user" and configures retry parameters.
6566
func newSSHConfig(host, keyPath string) *sshConfig {
6667
return &sshConfig{
6768
Host: host,
@@ -72,7 +73,9 @@ func newSSHConfig(host, keyPath string) *sshConfig {
7273
}
7374
}
7475

75-
// waitForSSHReady waits for SSH to become available on the server
76+
// waitForSSHReady waits for SSH to become available on the server.
77+
// It retries up to MaxRetries times with RetryDelay between attempts.
78+
// Returns an error if SSH doesn't become ready or if permission is denied.
7679
func waitForSSHReady(ctx context.Context, cfg *sshConfig) error {
7780
log.Debugf("Waiting for SSH to be ready on %s", cfg.Host)
7881

@@ -110,7 +113,8 @@ func waitForSSHReady(ctx context.Context, cfg *sshConfig) error {
110113
return fmt.Errorf("SSH not ready after %d attempts on %s", cfg.MaxRetries, cfg.Host)
111114
}
112115

113-
// execSSHCommand executes a command via SSH
116+
// execSSHCommand executes a command via SSH.
117+
// It returns the trimmed output and any error encountered.
114118
func execSSHCommand(cfg *sshConfig, command []string) (string, error) {
115119
args := []string{
116120
"ssh",
@@ -123,7 +127,8 @@ func execSSHCommand(cfg *sshConfig, command []string) (string, error) {
123127
return strings.TrimSpace(string(outb)), err
124128
}
125129

126-
// execSSHSudoCommand executes a command with sudo via SSH
130+
// execSSHSudoCommand executes a command with sudo via SSH.
131+
// The command is prefixed with "sudo" automatically.
127132
func execSSHSudoCommand(cfg *sshConfig, command []string) (string, error) {
128133
sudoCmd := append([]string{"sudo"}, command...)
129134
return execSSHCommand(cfg, sudoCmd)
@@ -133,7 +138,8 @@ func execSSHSudoCommand(cfg *sshConfig, command []string) (string, error) {
133138
// HAProxy Package Management
134139
// ============================================================================
135140

136-
// isHAProxyInstalled checks if HAProxy is installed on the remote server
141+
// isHAProxyInstalled checks if HAProxy is installed on the remote server.
142+
// It uses rpm -q to query the package database.
137143
func isHAProxyInstalled(cfg *sshConfig) (bool, error) {
138144
log.Debugf("Checking if HAProxy is installed on %s", cfg.Host)
139145

@@ -156,7 +162,7 @@ func isHAProxyInstalled(cfg *sshConfig) (bool, error) {
156162
return true, nil
157163
}
158164

159-
// installHAProxy installs HAProxy package on the remote server
165+
// installHAProxy installs HAProxy package on the remote server using dnf.
160166
func installHAProxy(cfg *sshConfig) error {
161167
log.Debugf("Installing HAProxy on %s", cfg.Host)
162168

@@ -172,7 +178,8 @@ func installHAProxy(cfg *sshConfig) error {
172178
return nil
173179
}
174180

175-
// ensureHAProxyInstalled ensures HAProxy is installed, installing if necessary
181+
// ensureHAProxyInstalled ensures HAProxy is installed, installing if necessary.
182+
// It checks if HAProxy is installed and installs it if not found.
176183
func ensureHAProxyInstalled(cfg *sshConfig) error {
177184
installed, err := isHAProxyInstalled(cfg)
178185
if err != nil {
@@ -190,7 +197,8 @@ func ensureHAProxyInstalled(cfg *sshConfig) error {
190197
// HAProxy Configuration Management
191198
// ============================================================================
192199

193-
// getFilePermissions retrieves file permissions in octal format
200+
// getFilePermissions retrieves file permissions in octal format using stat.
201+
// Returns a string like "644" or "755".
194202
func getFilePermissions(cfg *sshConfig, filePath string) (string, error) {
195203
output, err := execSSHSudoCommand(cfg, []string{
196204
"stat", "-c", "%a", filePath,
@@ -203,7 +211,8 @@ func getFilePermissions(cfg *sshConfig, filePath string) (string, error) {
203211
return output, nil
204212
}
205213

206-
// setFilePermissions sets file permissions
214+
// setFilePermissions sets file permissions using chmod.
215+
// The perms parameter should be in octal format (e.g., "644", "755").
207216
func setFilePermissions(cfg *sshConfig, filePath, perms string) error {
208217
log.Debugf("Setting permissions %s on %s", perms, filePath)
209218

@@ -218,7 +227,8 @@ func setFilePermissions(cfg *sshConfig, filePath, perms string) error {
218227
return nil
219228
}
220229

221-
// ensureHAProxyConfigPermissions ensures HAProxy config has correct permissions
230+
// ensureHAProxyConfigPermissions ensures HAProxy config has correct permissions.
231+
// It checks current permissions and updates them if they don't match the expected value.
222232
func ensureHAProxyConfigPermissions(cfg *sshConfig) error {
223233
currentPerms, err := getFilePermissions(cfg, haproxyConfigPath)
224234
if err != nil {
@@ -241,7 +251,8 @@ func ensureHAProxyConfigPermissions(cfg *sshConfig) error {
241251
// SELinux Configuration
242252
// ============================================================================
243253

244-
// getSELinuxBool retrieves the value of an SELinux boolean
254+
// getSELinuxBool retrieves the value of an SELinux boolean.
255+
// It parses the output format: "boolean_name --> on" or "boolean_name --> off".
245256
func getSELinuxBool(cfg *sshConfig, boolName string) (bool, error) {
246257
output, err := execSSHSudoCommand(cfg, []string{
247258
"getsebool", boolName,
@@ -258,7 +269,8 @@ func getSELinuxBool(cfg *sshConfig, boolName string) (bool, error) {
258269
return isOn, nil
259270
}
260271

261-
// setSELinuxBool sets an SELinux boolean persistently
272+
// setSELinuxBool sets an SELinux boolean persistently (-P flag).
273+
// The value is set to "1" for true and "0" for false.
262274
func setSELinuxBool(cfg *sshConfig, boolName string, value bool) error {
263275
valueStr := "0"
264276
if value {
@@ -278,7 +290,8 @@ func setSELinuxBool(cfg *sshConfig, boolName string, value bool) error {
278290
return nil
279291
}
280292

281-
// ensureHAProxySELinux ensures HAProxy SELinux settings are correct
293+
// ensureHAProxySELinux ensures HAProxy SELinux settings are correct.
294+
// It enables the haproxy_connect_any boolean if not already enabled.
282295
func ensureHAProxySELinux(cfg *sshConfig) error {
283296
isEnabled, err := getSELinuxBool(cfg, haproxySelinuxSetting)
284297
if err != nil {
@@ -298,7 +311,8 @@ func ensureHAProxySELinux(cfg *sshConfig) error {
298311
// Systemd Service Management
299312
// ============================================================================
300313

301-
// systemctlCommand executes a systemctl command
314+
// systemctlCommand executes a systemctl command via SSH.
315+
// Common actions include: enable, disable, start, stop, restart, status.
302316
func systemctlCommand(cfg *sshConfig, action, service string) error {
303317
log.Debugf("Executing systemctl %s %s", action, service)
304318

@@ -313,17 +327,18 @@ func systemctlCommand(cfg *sshConfig, action, service string) error {
313327
return nil
314328
}
315329

316-
// enableService enables a systemd service
330+
// enableService enables a systemd service to start on boot.
317331
func enableService(cfg *sshConfig, service string) error {
318332
return systemctlCommand(cfg, "enable", service)
319333
}
320334

321-
// startService starts a systemd service
335+
// startService starts a systemd service immediately.
322336
func startService(cfg *sshConfig, service string) error {
323337
return systemctlCommand(cfg, "start", service)
324338
}
325339

326-
// enableAndStartHAProxy enables and starts the HAProxy service
340+
// enableAndStartHAProxy enables and starts the HAProxy service.
341+
// It first enables the service to start on boot, then starts it immediately.
327342
func enableAndStartHAProxy(cfg *sshConfig) error {
328343
if err := enableService(cfg, haproxyServiceName); err != nil {
329344
return err
@@ -336,7 +351,14 @@ func enableAndStartHAProxy(cfg *sshConfig) error {
336351
// HAProxy Setup Orchestration
337352
// ============================================================================
338353

339-
// setupHAProxyOnServer performs complete HAProxy setup on the bastion server
354+
// setupHAProxyOnServer performs complete HAProxy setup on the bastion server.
355+
// It executes the following steps in order:
356+
// 1. Add server to known_hosts
357+
// 2. Wait for SSH to be ready
358+
// 3. Ensure HAProxy is installed
359+
// 4. Configure HAProxy file permissions
360+
// 5. Configure SELinux for HAProxy
361+
// 6. Enable and start HAProxy service
340362
func setupHAProxyOnServer(ctx context.Context, ipAddress, bastionRsa string) error {
341363
cfg := newSSHConfig(ipAddress, bastionRsa)
342364

@@ -374,7 +396,8 @@ func setupHAProxyOnServer(ctx context.Context, ipAddress, bastionRsa string) err
374396
return nil
375397
}
376398

377-
// getServerIPAddress extracts and validates the IP address from a server
399+
// getServerIPAddress extracts and validates the IP address from a server.
400+
// It returns an error if the IP address cannot be found or is empty.
378401
func getServerIPAddress(server servers.Server) (string, error) {
379402
_, ipAddress, err := findIpAddress(server)
380403
if err != nil {
@@ -549,9 +572,11 @@ func NewBastionConfigWithDefaults(enableHAProxy, shouldDebug bool) *BastionConfi
549572
}
550573
}
551574

552-
// parseBastionFlags extracts and validates flags into a BastionConfig
575+
// parseBastionFlags extracts and validates flags into a BastionConfig.
576+
// It parses command-line flags, populates the configuration, and validates it.
577+
// Returns an error if flag parsing fails or configuration is invalid.
553578
func parseBastionFlags(flags *flag.FlagSet, args []string) (*BastionConfig, error) {
554-
config := NewBastionConfig() // Use constructor with defaults
579+
config := NewBastionConfig() // Use constructor with defaults
555580

556581
// Define flags
557582
cloud := flags.String("cloud", "", "The cloud to use in clouds.yaml")
@@ -602,6 +627,14 @@ func parseBastionFlags(flags *flag.FlagSet, args []string) (*BastionConfig, erro
602627
return config, nil
603628
}
604629

630+
// createBastionCommand is the main entry point for the create-bastion command.
631+
// It orchestrates the entire bastion creation process:
632+
// 1. Parse and validate configuration
633+
// 2. Initialize logging
634+
// 3. Clean up previous bastion IP file
635+
// 4. Ensure server exists (create if needed)
636+
// 5. Setup bastion server (HAProxy, DNS)
637+
// 6. Write bastion IP to file
605638
func createBastionCommand(createBastionFlags *flag.FlagSet, args []string) error {
606639
// Print version info
607640
fmt.Fprintf(os.Stderr, "Program version is %v, release = %v\n", version, release)
@@ -614,7 +647,7 @@ func createBastionCommand(createBastionFlags *flag.FlagSet, args []string) error
614647

615648
// Initialize logger
616649
log = initLogger(config.ShouldDebug)
617-
if shouldDebug {
650+
if config.ShouldDebug {
618651
log.Debugf("Debug mode enabled")
619652
}
620653

@@ -645,7 +678,8 @@ func createBastionCommand(createBastionFlags *flag.FlagSet, args []string) error
645678
return nil
646679
}
647680

648-
// cleanupBastionIPFile removes the bastion IP file if it exists
681+
// cleanupBastionIPFile removes the bastion IP file if it exists.
682+
// It returns an error only if the file exists but cannot be removed.
649683
func cleanupBastionIPFile() error {
650684
err := os.Remove(bastionIpFilename)
651685
if err != nil && !os.IsNotExist(err) {
@@ -654,18 +688,19 @@ func cleanupBastionIPFile() error {
654688
return nil
655689
}
656690

657-
// ensureServerExists checks if server exists and creates it if needed
691+
// ensureServerExists checks if server exists and creates it if needed.
692+
// If the server already exists, it returns immediately.
693+
// If the server doesn't exist, it creates it and verifies the creation.
658694
func ensureServerExists(ctx context.Context, config *BastionConfig) error {
659695
_, err := findServer(ctx, config.Cloud, config.BastionName)
660696
if err == nil {
661697
log.Debugf("Server %s already exists", config.BastionName)
662698
return nil
663699
}
664700

665-
// This does not work!
666-
// if !errors.Is(err, ErrServerNotFound) {
667-
// This does
668-
if !strings.HasPrefix(strings.ToLower(err.Error()), strings.ToLower("Could not find server named")) {
701+
// Check if error is "server not found" - using string prefix check as errors.Is doesn't work
702+
// with the current error wrapping in findServer
703+
if !strings.HasPrefix(strings.ToLower(err.Error()), "could not find server named") {
669704
return fmt.Errorf("failed to find server: %w", err)
670705
}
671706

@@ -694,7 +729,10 @@ func ensureServerExists(ctx context.Context, config *BastionConfig) error {
694729
return nil
695730
}
696731

697-
// setupBastion configures the bastion server either remotely or locally
732+
// setupBastion configures the bastion server either remotely or locally.
733+
// The setup mode is determined by the BastionConfig:
734+
// - Remote setup: delegates to another server via sendCreateBastion
735+
// - Local setup: performs setup directly via SSH
698736
func setupBastion(ctx context.Context, config *BastionConfig) error {
699737
if config.IsRemoteSetup() {
700738
fmt.Println("Setting up bastion remotely...")
@@ -772,7 +810,8 @@ func createServer(ctx context.Context, cloudName, flavorName, imageName, network
772810
return nil
773811
}
774812

775-
// createNetworkPort creates a network port for the server
813+
// createNetworkPort creates a network port for the server.
814+
// The port is named "<bastionName>-port" and attached to the specified network.
776815
func createNetworkPort(ctx context.Context, cloudName, bastionName, networkID string) (*ports.Port, error) {
777816
connNetwork, err := NewServiceClient(ctx, "network", DefaultClientOpts(cloudName))
778817
if err != nil {
@@ -793,7 +832,8 @@ func createNetworkPort(ctx context.Context, cloudName, bastionName, networkID st
793832
return port, nil
794833
}
795834

796-
// createServerInstance creates the actual server instance
835+
// createServerInstance creates the actual server instance.
836+
// If sshKeyName is empty, the server is created without an SSH key.
797837
func createServerInstance(ctx context.Context, cloudName, bastionName, flavorID, imageID, portID, sshKeyName, sshKeyPairName string, userData []byte) (*servers.Server, error) {
798838
connCompute, err := NewServiceClient(ctx, "compute", DefaultClientOpts(cloudName))
799839
if err != nil {
@@ -829,7 +869,8 @@ func createServerInstance(ctx context.Context, cloudName, bastionName, flavorID,
829869
return newServer, nil
830870
}
831871

832-
// addServerKnownHosts adds the server's SSH host keys to known_hosts file
872+
// addServerKnownHosts adds the server's SSH host keys to known_hosts file.
873+
// It removes any existing host keys for the IP address before adding new ones.
833874
func addServerKnownHosts(ctx context.Context, ipAddress string) error {
834875
homeDir, err := os.UserHomeDir()
835876
if err != nil {
@@ -859,7 +900,8 @@ func addServerKnownHosts(ctx context.Context, ipAddress string) error {
859900
return nil
860901
}
861902

862-
// removeHostKey removes a host's key from known_hosts file
903+
// removeHostKey removes a host's key from known_hosts file.
904+
// This function intentionally ignores errors as the host key may not exist.
863905
func removeHostKey(knownHostsPath, ipAddress string) error {
864906
outb, _ := runSplitCommand2([]string{
865907
"ssh-keygen",
@@ -870,7 +912,8 @@ func removeHostKey(knownHostsPath, ipAddress string) error {
870912
return nil
871913
}
872914

873-
// appendToFile appends data to a file
915+
// appendToFile appends data to a file.
916+
// It returns an error if the file cannot be opened, written to, or if the write is incomplete.
874917
func appendToFile(filePath string, data []byte) error {
875918
file, err := os.OpenFile(filePath, os.O_APPEND|os.O_RDWR, filePermReadWrite)
876919
if err != nil {
@@ -890,8 +933,12 @@ func appendToFile(filePath string, data []byte) error {
890933
return nil
891934
}
892935

893-
// setupBastionServer orchestrates bastion server configuration
894-
// This is the refactored main function - much cleaner and easier to understand
936+
// setupBastionServer orchestrates bastion server configuration.
937+
// It performs the following steps:
938+
// 1. Find the server in OpenStack
939+
// 2. Extract and validate the server's IP address
940+
// 3. Setup HAProxy if enabled
941+
// 4. Configure DNS records if IBM Cloud API key is available
895942
func setupBastionServer(ctx context.Context, enableHAProxy bool, cloudName, serverName, domainName, bastionRsa string) error {
896943
// Step 1: Find the server
897944
server, err := findServer(ctx, cloudName, serverName)
@@ -930,7 +977,8 @@ func setupBastionServer(ctx context.Context, enableHAProxy bool, cloudName, serv
930977
return nil
931978
}
932979

933-
// writeBastionIP writes the bastion server's IP address to a file
980+
// writeBastionIP writes the bastion server's IP address to a file.
981+
// The IP address is written to the file specified by bastionIpFilename constant.
934982
func writeBastionIP(ctx context.Context, cloudName, serverName string) error {
935983
server, err := findServer(ctx, cloudName, serverName)
936984
if err != nil {
@@ -951,15 +999,17 @@ func writeBastionIP(ctx context.Context, cloudName, serverName string) error {
951999
return nil
9521000
}
9531001

954-
// removeCommentLines filters out lines starting with '#' from input text
1002+
// removeCommentLines filters out lines starting with '#' from input text.
1003+
// Empty lines are also removed. The function pre-allocates capacity for efficiency.
9551004
func removeCommentLines(input string) string {
9561005
var builder strings.Builder
9571006
builder.Grow(len(input)) // Pre-allocate capacity
9581007

9591008
lines := strings.Split(input, "\n")
960-
for i, line := range lines {
961-
if !strings.HasPrefix(strings.TrimSpace(line), "#") && line != "" {
962-
if i > 0 && builder.Len() > 0 {
1009+
for _, line := range lines {
1010+
trimmed := strings.TrimSpace(line)
1011+
if trimmed != "" && !strings.HasPrefix(trimmed, "#") {
1012+
if builder.Len() > 0 {
9631013
builder.WriteByte('\n')
9641014
}
9651015
builder.WriteString(line)
@@ -976,7 +1026,11 @@ type dnsRecord struct {
9761026
content string
9771027
}
9781028

979-
// dnsForServer configures DNS records for the bastion server
1029+
// dnsForServer configures DNS records for the bastion server.
1030+
// It creates three DNS records:
1031+
// 1. A record for api.<bastionName>.<domainName>
1032+
// 2. A record for api-int.<bastionName>.<domainName>
1033+
// 3. CNAME record for *.apps.<bastionName>.<domainName> pointing to api
9801034
func dnsForServer(ctx context.Context, cloudName, apiKey, bastionName, domainName string) error {
9811035
// Step 1: Get server IP address
9821036
server, err := findServer(ctx, cloudName, bastionName)

0 commit comments

Comments
 (0)