Update logging constructs for Session Manger driver

This commit is contained in:
Wilken Rivera 2020-05-06 15:21:08 -04:00
parent 81c40b8d08
commit 0d13c634b7
2 changed files with 58 additions and 37 deletions

View File

@ -1,14 +1,15 @@
package common
import (
"bytes"
"context"
"encoding/json"
"fmt"
"log"
"os/exec"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ssm"
"github.com/mitchellh/iochan"
)
const sessionManagerPluginName string = "session-manager-plugin"
@ -22,62 +23,89 @@ type SSMDriver struct {
Session *ssm.StartSessionOutput
SessionParams ssm.StartSessionInput
SessionEndpoint string
// Provided for testing purposes; if not specified it defaults to sessionManagerPluginName
PluginName string
PluginName string
}
// StartSession starts an interactive Systems Manager session with a remote instance via the AWS session-manager-plugin
func (sd *SSMDriver) StartSession(ctx context.Context) error {
var stdout bytes.Buffer
var stderr bytes.Buffer
if sd.PluginName == "" {
sd.PluginName = sessionManagerPluginName
func (d *SSMDriver) StartSession(ctx context.Context) error {
if d.PluginName == "" {
d.PluginName = sessionManagerPluginName
}
args, err := sd.Args()
args, err := d.Args()
if err != nil {
err = fmt.Errorf("error encountered validating session details: %s", err)
return err
}
cmd := exec.CommandContext(ctx, sd.PluginName, args...)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd := exec.CommandContext(ctx, d.PluginName, args...)
if err := cmd.Start(); err != nil {
err = fmt.Errorf("error encountered when calling %s: %s\nStderr: %s", sd.PluginName, err, stderr.String())
// Let's build up our logging
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
// Create the channels we'll use for data
stdoutCh := iochan.DelimReader(stdout, '\n')
stderrCh := iochan.DelimReader(stderr, '\n')
// Loop and get all our output
go func(ctx context.Context, prefix string) {
for {
select {
case <-ctx.Done():
return
case output := <-stderrCh:
if output != "" {
log.Printf("[ERROR] %s: %s", prefix, output)
}
case output := <-stdoutCh:
if output != "" {
log.Printf("[DEBUG] %s: %s", prefix, output)
}
}
}
}(ctx, d.PluginName)
log.Printf("[DEBUG %s] opening session tunnel to instance %q for session %q", d.PluginName, aws.StringValue(d.SessionParams.Target), aws.StringValue(d.Session.SessionId))
if err := cmd.Start(); err != nil {
err = fmt.Errorf("error encountered when calling %s: %s\n", d.PluginName, err)
return err
}
// TODO capture logging for testing
log.Println(stdout.String())
return nil
}
func (sd *SSMDriver) Args() ([]string, error) {
if sd.Session == nil {
func (d *SSMDriver) Args() ([]string, error) {
if d.Session == nil {
return nil, fmt.Errorf("an active Amazon SSM Session is required before trying to open a session tunnel")
}
// AWS session-manager-plugin requires a valid session be passed in JSON.
sessionDetails, err := json.Marshal(sd.Session)
sessionDetails, err := json.Marshal(d.Session)
if err != nil {
return nil, fmt.Errorf("error encountered in reading session details %s", err)
}
// AWS session-manager-plugin requires the parameters used in the session to be passed in JSON as well.
sessionParameters, err := json.Marshal(sd.SessionParams)
sessionParameters, err := json.Marshal(d.SessionParams)
if err != nil {
return nil, fmt.Errorf("error encountered in reading session parameter details %s", err)
}
// Args must be in this order
args := []string{
string(sessionDetails),
sd.Region,
d.Region,
sessionCommand,
sd.ProfileName,
d.ProfileName,
string(sessionParameters),
sd.SessionEndpoint,
d.SessionEndpoint,
}
return args, nil

View File

@ -50,15 +50,14 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
}
s.instanceId = aws.StringValue(instance.InstanceId)
log.Printf("Starting PortForwarding session to instance %q on local port %q to remote port %q", s.instanceId, s.LocalPortNumber, s.RemotePortNumber)
log.Printf("Starting PortForwarding session to instance %q on local port %d to remote port %d", s.instanceId, s.LocalPortNumber, s.RemotePortNumber)
input := s.BuildTunnelInputForInstance(s.instanceId)
ssmconn := ssm.New(s.AWSSession)
var output *ssm.StartSessionOutput
err := retry.Config{
ShouldRetry: func(err error) bool { return isAWSErr(err, "TargetNotConnected", "") },
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
}.Run(ctx, func(ctx context.Context) (err error) {
output, err = ssmconn.StartSessionWithContext(ctx, &input)
s.session, err = ssmconn.StartSessionWithContext(ctx, &input)
return err
})
@ -71,7 +70,7 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
driver := SSMDriver{
Region: s.Region,
Session: output,
Session: s.session,
SessionParams: input,
SessionEndpoint: ssmconn.Endpoint,
}
@ -83,27 +82,21 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
return multistep.ActionHalt
}
ui.Message(fmt.Sprintf("PortForwarding session tunnel to instance %q established!", s.instanceId))
ui.Message(fmt.Sprintf("PortForwarding session %q to instance %q has been started", aws.StringValue(s.session.SessionId), s.instanceId))
state.Put("sessionPort", s.LocalPortNumber)
return multistep.ActionContinue
}
// Cleanup terminates an active session on AWS, which in turn terminates the associated tunnel process running on the local machine.
func (s *StepCreateSSMTunnel) Cleanup(state multistep.StateBag) {
if s.session == nil {
return
}
ui := state.Get("ui").(packer.Ui)
ssmconn := ssm.New(s.AWSSession)
_, err := ssmconn.TerminateSession(&ssm.TerminateSessionInput{SessionId: s.session.SessionId})
if err != nil {
msg := fmt.Sprintf("Error terminating SSM Session %q. Please terminate the session manually: %s",
aws.StringValue(s.session.SessionId), err)
msg := fmt.Sprintf("Error terminating SSM Session %q. Please terminate the session manually: %s", aws.StringValue(s.session.SessionId), err)
ui.Error(msg)
}
}
// ConfigureLocalHostPort finds an available port on the localhost that can be used for the remote tunnel.