I'm trying to develop a functionality on a Go tool that I wrote to facilitate AWS CLI usage. Basically, the goal is to dump a MySQL database through AWS SSM by using port forwarding.
First of all, it works fine when I do the commands manually.
aws ssm start-session --document-name SSM-Document --target i-target --region eu-west-3 --parameters '{"host":["myhost.eu-west-3.rds.amazonaws.com"]}'
This command above gives that kind of output:
Starting session with SessionId:
Port 3306 opened for sessionId
Waiting for connections...
In an other shell, locally, on my computer, not on RDS instance, I can do the dump without any problem:
mysqldump -h 127.0.0.1 -u user -p password dbname --result-file=dump.sql
and that's it. The dump file is generated.
But when I try to do something similar in my Golang code I have the following error:
2023/07/18 09:51:28 SSM session started successfully. User: i-target, Instance ID: , Session ID:, Connection Status: Connected
2023/07/18 09:51:28 Executing mysqldump command: /opt/homebrew/bin/mysqldump -h 127.0.0.1 -P3306 -u user -p password dbname --result-file=dump.sql
2023/07/18 09:51:28 mysqldump command failed with error: exit status 2
mysqldump: Got error: 2003: Can't connect to MySQL server on '127.0.0.1:3306' (61) when trying to connect
2023/07/18 09:51:28 SSM session status: Connected
Here is the Golang code:
func DumpDatabase(ctx context.Context, host, appServerId, port, user, password, dbName, fileName string) error {
utils.LoadAWSEnv()
sess := session.Must(session.NewSessionWithOptions(session.Options{
SharedConfigState: session.SharedConfigEnable,
}))
// Create an RDS service client
svc := rds.New(sess)
// Get the endpoint for the specified RDS instance
params := &rds.DescribeDBInstancesInput{
DBInstanceIdentifier: aws.String(host),
}
rdsResp, err := svc.DescribeDBInstancesWithContext(ctx, params)
if err != nil {
return fmt.Errorf("error describing DB instance: %v", err)
}
if len(rdsResp.DBInstances) == 0 {
return fmt.Errorf("DB instance not found")
}
endpoint := rdsResp.DBInstances[0].Endpoint.Address
// Create a wait group to wait for the SSM session and mysqldump command to complete
var wg sync.WaitGroup
wg.Add(2)
// Create a context to manage the SSM session and mysqldump command
ssmCtx, ssmCancel := context.WithCancel(ctx)
defer ssmCancel()
// Create a channel to signal when the SSM session has started
ssmStarted := make(chan error)
// Create a channel to signal when the mysqldump command has completed
mysqldumpDone := make(chan error)
// Declare the SSM service client and SSM session output variables
var ssmSvc *ssm.SSM
var ssmSession *ssm.Session
// Start the SSM session in a goroutine
go func() {
defer wg.Done()
// Create an SSM service client
ssmSvc = ssm.New(sess)
// Start the SSM session
_, err := ssmSvc.StartSessionWithContext(ssmCtx, &ssm.StartSessionInput{
DocumentName: aws.String("SSM-Document"),
Target: aws.String(appServerId),
Parameters: map[string][]*string{"host": {aws.String(*endpoint)}},
})
if err != nil {
log.Println("Error starting SSM session:", err)
mysqldumpDone <- fmt.Errorf("error starting SSM session: %v", err)
return
}
// Wait for the SSM session to start
for {
output, err := ssmSvc.DescribeSessionsWithContext(ssmCtx, &ssm.DescribeSessionsInput{
State: aws.String("Active"),
})
if err != nil {
log.Println("Error describing SSM session:", err)
mysqldumpDone <- fmt.Errorf("error describing SSM session: %v", err)
return
}
if len(output.Sessions) == 0 {
log.Println("SSM session not found")
mysqldumpDone <- fmt.Errorf("SSM session not found")
return
}
if *output.Sessions[0].Status == "Connected" {
ssmSession = output.Sessions[0]
log.Printf("SSM session started successfully. User: %s, Instance ID: %s, Session ID: %s, Connection Status: %s\n", *ssmSession.Target, *ssmSession.SessionId, *ssmSession.SessionId, *ssmSession.Status)
break
}
log.Println("Waiting for SSM session to start...")
time.Sleep(5 * time.Second)
}
// Signal that the SSM session has started
ssmStarted <- nil
}()
// Start the mysqldump command in a goroutine
go func() {
defer wg.Done()
// Wait for the SSM session to start
<-ssmStarted
// Start the mysqldump command
dumpCmd := exec.CommandContext(ssmCtx, "mysqldump", "-h127.0.0.1", "-P"+port, "-u"+user, "-p"+password, dbName, "--result-file="+fileName)
//dumpCmd.Env = append(os.Environ(), "MYSQL_TCP_PORT="+port)
log.Println("Executing mysqldump command:", dumpCmd.String())
output, err := dumpCmd.CombinedOutput()
if err != nil {
log.Println("mysqldump command failed with error:", err)
log.Println("output:", string(output))
log.Println("SSM session status:", *ssmSession.Status)
// Signal that the mysqldump command has completed with an error
mysqldumpDone <- fmt.Errorf("mysqldump command failed with error: %v, SSM session status: %s", err, *ssmSession.Status)
return
}
// Signal that the mysqldump command has completed successfully
mysqldumpDone <- nil
}()
// Wait for the SSM session and mysqldump command to complete
wg.Wait()
// Check if the mysqldump command completed with an error
err = <-mysqldumpDone
if err != nil {
return err
}
return nil
}
Initially I thought that my issue was related to the SSM session that was closed before doing the actual mysqldump command but it seems that the session is still active even after the error. Or maybe the issue is that the mysqldump command is not running as a "detached" process.