Update logging constructs for Session Manger driver
This commit is contained in:
parent
81c40b8d08
commit
0d13c634b7
|
@ -1,14 +1,15 @@
|
||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/service/ssm"
|
"github.com/aws/aws-sdk-go/service/ssm"
|
||||||
|
"github.com/mitchellh/iochan"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sessionManagerPluginName string = "session-manager-plugin"
|
const sessionManagerPluginName string = "session-manager-plugin"
|
||||||
|
@ -22,62 +23,89 @@ type SSMDriver struct {
|
||||||
Session *ssm.StartSessionOutput
|
Session *ssm.StartSessionOutput
|
||||||
SessionParams ssm.StartSessionInput
|
SessionParams ssm.StartSessionInput
|
||||||
SessionEndpoint string
|
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
|
// 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 {
|
func (d *SSMDriver) StartSession(ctx context.Context) error {
|
||||||
var stdout bytes.Buffer
|
if d.PluginName == "" {
|
||||||
var stderr bytes.Buffer
|
d.PluginName = sessionManagerPluginName
|
||||||
|
|
||||||
if sd.PluginName == "" {
|
|
||||||
sd.PluginName = sessionManagerPluginName
|
|
||||||
}
|
}
|
||||||
|
|
||||||
args, err := sd.Args()
|
args, err := d.Args()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("error encountered validating session details: %s", err)
|
err = fmt.Errorf("error encountered validating session details: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, sd.PluginName, args...)
|
cmd := exec.CommandContext(ctx, d.PluginName, args...)
|
||||||
cmd.Stdout = &stdout
|
|
||||||
cmd.Stderr = &stderr
|
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
// Let's build up our logging
|
||||||
err = fmt.Errorf("error encountered when calling %s: %s\nStderr: %s", sd.PluginName, err, stderr.String())
|
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
|
return err
|
||||||
}
|
}
|
||||||
// TODO capture logging for testing
|
|
||||||
log.Println(stdout.String())
|
|
||||||
|
|
||||||
return nil
|
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")
|
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.
|
// 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error encountered in reading session details %s", err)
|
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.
|
// 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error encountered in reading session parameter details %s", err)
|
return nil, fmt.Errorf("error encountered in reading session parameter details %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Args must be in this order
|
||||||
args := []string{
|
args := []string{
|
||||||
string(sessionDetails),
|
string(sessionDetails),
|
||||||
sd.Region,
|
d.Region,
|
||||||
sessionCommand,
|
sessionCommand,
|
||||||
sd.ProfileName,
|
d.ProfileName,
|
||||||
string(sessionParameters),
|
string(sessionParameters),
|
||||||
sd.SessionEndpoint,
|
d.SessionEndpoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
return args, nil
|
return args, nil
|
||||||
|
|
|
@ -50,15 +50,14 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
|
||||||
}
|
}
|
||||||
s.instanceId = aws.StringValue(instance.InstanceId)
|
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)
|
input := s.BuildTunnelInputForInstance(s.instanceId)
|
||||||
ssmconn := ssm.New(s.AWSSession)
|
ssmconn := ssm.New(s.AWSSession)
|
||||||
var output *ssm.StartSessionOutput
|
|
||||||
err := retry.Config{
|
err := retry.Config{
|
||||||
ShouldRetry: func(err error) bool { return isAWSErr(err, "TargetNotConnected", "") },
|
ShouldRetry: func(err error) bool { return isAWSErr(err, "TargetNotConnected", "") },
|
||||||
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
|
RetryDelay: (&retry.Backoff{InitialBackoff: 200 * time.Millisecond, MaxBackoff: 60 * time.Second, Multiplier: 2}).Linear,
|
||||||
}.Run(ctx, func(ctx context.Context) (err error) {
|
}.Run(ctx, func(ctx context.Context) (err error) {
|
||||||
output, err = ssmconn.StartSessionWithContext(ctx, &input)
|
s.session, err = ssmconn.StartSessionWithContext(ctx, &input)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -71,7 +70,7 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
|
||||||
|
|
||||||
driver := SSMDriver{
|
driver := SSMDriver{
|
||||||
Region: s.Region,
|
Region: s.Region,
|
||||||
Session: output,
|
Session: s.session,
|
||||||
SessionParams: input,
|
SessionParams: input,
|
||||||
SessionEndpoint: ssmconn.Endpoint,
|
SessionEndpoint: ssmconn.Endpoint,
|
||||||
}
|
}
|
||||||
|
@ -83,27 +82,21 @@ func (s *StepCreateSSMTunnel) Run(ctx context.Context, state multistep.StateBag)
|
||||||
return multistep.ActionHalt
|
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)
|
state.Put("sessionPort", s.LocalPortNumber)
|
||||||
|
|
||||||
return multistep.ActionContinue
|
return multistep.ActionContinue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup terminates an active session on AWS, which in turn terminates the associated tunnel process running on the local machine.
|
// 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) {
|
func (s *StepCreateSSMTunnel) Cleanup(state multistep.StateBag) {
|
||||||
if s.session == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ui := state.Get("ui").(packer.Ui)
|
ui := state.Get("ui").(packer.Ui)
|
||||||
|
|
||||||
ssmconn := ssm.New(s.AWSSession)
|
ssmconn := ssm.New(s.AWSSession)
|
||||||
_, err := ssmconn.TerminateSession(&ssm.TerminateSessionInput{SessionId: s.session.SessionId})
|
_, err := ssmconn.TerminateSession(&ssm.TerminateSessionInput{SessionId: s.session.SessionId})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
msg := fmt.Sprintf("Error terminating SSM Session %q. Please terminate the session manually: %s",
|
msg := fmt.Sprintf("Error terminating SSM Session %q. Please terminate the session manually: %s", aws.StringValue(s.session.SessionId), err)
|
||||||
aws.StringValue(s.session.SessionId), err)
|
|
||||||
ui.Error(msg)
|
ui.Error(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureLocalHostPort finds an available port on the localhost that can be used for the remote tunnel.
|
// ConfigureLocalHostPort finds an available port on the localhost that can be used for the remote tunnel.
|
||||||
|
|
Loading…
Reference in New Issue