@@ -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.
6566func 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.
7679func 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.
114118func 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.
127132func 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.
137143func 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.
160166func 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.
176183func 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".
194202func 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").
207216func 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.
222232func 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".
245256func 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.
262274func 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.
282295func 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.
302316func 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.
317331func 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.
322336func 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.
327342func 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
340362func 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.
378401func 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.
553578func 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
605638func 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.
649683func 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.
658694func 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
698736func 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.
776815func 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.
797837func 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.
833874func 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.
863905func 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.
874917func 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
895942func 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.
934982func 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.
9551004func 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
9801034func 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