github.com/openshift/installer@v1.4.17/pkg/gather/aws/aws.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/aws/aws-sdk-go/aws"
    13  	"github.com/aws/aws-sdk-go/aws/awserr"
    14  	"github.com/aws/aws-sdk-go/aws/request"
    15  	"github.com/aws/aws-sdk-go/aws/session"
    16  	"github.com/aws/aws-sdk-go/service/ec2"
    17  	"github.com/pkg/errors"
    18  	"github.com/sirupsen/logrus"
    19  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    20  
    21  	awssession "github.com/openshift/installer/pkg/asset/installconfig/aws"
    22  	"github.com/openshift/installer/pkg/gather"
    23  	"github.com/openshift/installer/pkg/gather/providers"
    24  	"github.com/openshift/installer/pkg/types"
    25  	"github.com/openshift/installer/pkg/version"
    26  )
    27  
    28  // Filter holds the key/value pairs for the tags we will be matching against.
    29  //
    30  // A resource matches the filter if all of the key/value pairs are in its tags.
    31  type Filter map[string]string
    32  
    33  // Gather holds options for resources we want to gather.
    34  type Gather struct {
    35  	logger          logrus.FieldLogger
    36  	filters         []Filter
    37  	region          string
    38  	bootstrap       string
    39  	masters         []string
    40  	directory       string
    41  	serialLogBundle string
    42  
    43  	// Session is the AWS session to be used for gathering. If nil, a new
    44  	// session will be created based on the usual credential configuration
    45  	// (AWS_PROFILE, AWS_ACCESS_KEY_ID, etc.).
    46  	session *session.Session
    47  }
    48  
    49  // New returns an AWS Gather from ClusterMetadata.
    50  func New(logger logrus.FieldLogger, serialLogBundle string, bootstrap string, masters []string, metadata *types.ClusterMetadata) (providers.Gather, error) {
    51  	filters := make([]Filter, 0, len(metadata.ClusterPlatformMetadata.AWS.Identifier))
    52  	for _, filter := range metadata.ClusterPlatformMetadata.AWS.Identifier {
    53  		filters = append(filters, filter)
    54  	}
    55  	region := metadata.ClusterPlatformMetadata.AWS.Region
    56  	session, err := awssession.GetSessionWithOptions(
    57  		awssession.WithRegion(region),
    58  		awssession.WithServiceEndpoints(region, metadata.ClusterPlatformMetadata.AWS.ServiceEndpoints),
    59  	)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  
    64  	return &Gather{
    65  		logger:          logger,
    66  		region:          region,
    67  		filters:         filters,
    68  		session:         session,
    69  		serialLogBundle: serialLogBundle,
    70  		bootstrap:       bootstrap,
    71  		masters:         masters,
    72  		directory:       filepath.Dir(serialLogBundle),
    73  	}, nil
    74  }
    75  
    76  // Run is the entrypoint to start the gather process.
    77  func (g *Gather) Run() error {
    78  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
    79  	defer cancel()
    80  
    81  	awsSession := g.session
    82  	if awsSession == nil {
    83  		var err error
    84  		// Relying on appropriate AWS ENV vars (eg AWS_PROFILE, AWS_ACCESS_KEY_ID, etc)
    85  		awsSession, err = session.NewSession(aws.NewConfig().WithRegion(g.region))
    86  		if err != nil {
    87  			return err
    88  		}
    89  	}
    90  	awsSession.Handlers.Build.PushBackNamed(request.NamedHandler{
    91  		Name: "openshiftInstaller.OpenshiftInstallerUserAgentHandler",
    92  		Fn:   request.MakeAddToUserAgentHandler("OpenShift/4.x Gather", version.Raw),
    93  	})
    94  
    95  	ec2Client := ec2.New(awsSession)
    96  
    97  	instances, err := g.findEC2Instances(ctx, ec2Client)
    98  	if err != nil {
    99  		return err
   100  	}
   101  
   102  	if len(instances) == 0 {
   103  		g.logger.Infoln("Skipping console log gathering: no instances found")
   104  		return nil
   105  	}
   106  
   107  	serialLogBundleDir := strings.TrimSuffix(filepath.Base(g.serialLogBundle), ".tar.gz")
   108  	filePathDir := filepath.Join(g.directory, serialLogBundleDir)
   109  	err = os.MkdirAll(filePathDir, 0755)
   110  	if err != nil && !errors.Is(err, os.ErrExist) {
   111  		return err
   112  	}
   113  
   114  	var errs []error
   115  	var files []string
   116  	for _, instance := range instances {
   117  		filePath, err := g.downloadConsoleOutput(ctx, ec2Client, instance, filePathDir)
   118  		if err != nil {
   119  			errs = append(errs, err)
   120  		} else {
   121  			files = append(files, filePath)
   122  		}
   123  	}
   124  
   125  	if len(files) > 0 {
   126  		err := gather.CreateArchive(files, g.serialLogBundle)
   127  		if err != nil {
   128  			errs = append(errs, errors.Wrap(err, "failed to create archive"))
   129  		}
   130  	}
   131  
   132  	if err := gather.DeleteArchiveDirectory(filePathDir); err != nil {
   133  		// Note: cleanup is best effort, it shouldn't fail the gather
   134  		g.logger.Debugf("Failed to remove archive directory: %v", err)
   135  	}
   136  
   137  	return utilerrors.NewAggregate(errs)
   138  }
   139  
   140  // findEC2Instances returns the EC2 instances with tags that satisfy the filters.
   141  func (g *Gather) findEC2Instances(ctx context.Context, ec2Client *ec2.EC2) ([]*ec2.Instance, error) {
   142  	if ec2Client.Config.Region == nil {
   143  		return nil, errors.New("EC2 client does not have region configured")
   144  	}
   145  
   146  	var instances []*ec2.Instance
   147  	for _, filter := range g.filters {
   148  		g.logger.Debugf("Search for matching instances by tag in %s matching %#+v", *ec2Client.Config.Region, filter)
   149  		instanceFilters := make([]*ec2.Filter, 0, len(g.filters))
   150  		for key, value := range filter {
   151  			instanceFilters = append(instanceFilters, &ec2.Filter{
   152  				Name:   aws.String("tag:" + key),
   153  				Values: []*string{aws.String(value)},
   154  			})
   155  		}
   156  
   157  		err := ec2Client.DescribeInstancesPagesWithContext(
   158  			ctx,
   159  			&ec2.DescribeInstancesInput{Filters: instanceFilters},
   160  			func(results *ec2.DescribeInstancesOutput, lastPage bool) bool {
   161  				for _, reservation := range results.Reservations {
   162  					if reservation.OwnerId == nil {
   163  						continue
   164  					}
   165  
   166  					for _, instance := range reservation.Instances {
   167  						if instance.InstanceId != nil {
   168  							instances = append(instances, instance)
   169  						}
   170  					}
   171  				}
   172  				return !lastPage
   173  			},
   174  		)
   175  		if err != nil {
   176  			err = errors.Wrap(err, "get ec2 instances")
   177  			return instances, err
   178  		}
   179  	}
   180  
   181  	return instances, nil
   182  }
   183  
   184  func (g *Gather) downloadConsoleOutput(ctx context.Context, ec2Client *ec2.EC2, instance *ec2.Instance, filePathDir string) (string, error) {
   185  	logger := g.logger.WithField("Instance", aws.StringValue(instance.InstanceId))
   186  
   187  	input := &ec2.GetConsoleOutputInput{
   188  		InstanceId: instance.InstanceId,
   189  	}
   190  	result, err := ec2Client.GetConsoleOutputWithContext(ctx, input)
   191  	if err != nil {
   192  		// Cast err to awserr.Error to get the Message from an error.
   193  		if aerr, ok := err.(awserr.Error); ok {
   194  			logger.Errorln(aerr.Error())
   195  		}
   196  		return "", err
   197  	}
   198  
   199  	instanceName := aws.StringValue(result.InstanceId)
   200  	for _, tags := range instance.Tags {
   201  		if strings.EqualFold(aws.StringValue(tags.Key), "Name") {
   202  			instanceName = aws.StringValue(tags.Value)
   203  		}
   204  	}
   205  
   206  	logger.Debugf("Attemping to download console logs for %s", instanceName)
   207  	filePath, err := g.saveToFile(instanceName, aws.StringValue(result.Output), filePathDir)
   208  	if err != nil {
   209  		return "", err
   210  	}
   211  	logger.Debug("Download complete")
   212  
   213  	return filePath, nil
   214  }
   215  
   216  func (g *Gather) saveToFile(instanceName, content, filePathDir string) (string, error) {
   217  	data, err := base64.StdEncoding.DecodeString(content)
   218  	if err != nil {
   219  		return "", errors.Wrap(err, "failed to decode console output")
   220  	}
   221  
   222  	filename := filepath.Join(filePathDir, fmt.Sprintf("%s-serial.log", instanceName))
   223  
   224  	file, err := os.Create(filename)
   225  	if err != nil {
   226  		return "", errors.Wrap(err, "failed to create file")
   227  	}
   228  	defer file.Close()
   229  
   230  	_, err = file.Write(data)
   231  	if err != nil {
   232  		return "", errors.Wrap(err, "failed to write to file")
   233  	}
   234  
   235  	return filename, nil
   236  }