github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/cmd/mdbd/loadAws.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  
     7  	"github.com/Cloud-Foundations/Dominator/lib/awsutil"
     8  	libjson "github.com/Cloud-Foundations/Dominator/lib/json"
     9  	"github.com/Cloud-Foundations/Dominator/lib/log"
    10  	"github.com/Cloud-Foundations/Dominator/lib/mdb"
    11  	"github.com/aws/aws-sdk-go/aws"
    12  	"github.com/aws/aws-sdk-go/service/ec2"
    13  )
    14  
    15  type awsGeneratorType struct {
    16  	targets        awsutil.TargetList
    17  	filterTagsFile string
    18  }
    19  
    20  type resultType struct {
    21  	mdb *mdb.Mdb
    22  	err error
    23  }
    24  
    25  type tagFilterType struct {
    26  	Key    string
    27  	Values []string
    28  }
    29  
    30  var credentialsStore *awsutil.CredentialsStore
    31  
    32  func loadCredentials() error {
    33  	if credentialsStore == nil {
    34  		var err error
    35  		credentialsStore, err = awsutil.LoadCredentials()
    36  		if err != nil {
    37  			return err
    38  		}
    39  	}
    40  	return nil
    41  }
    42  
    43  func newAwsGenerator(args []string, logger log.DebugLogger) (generator, error) {
    44  	if err := loadCredentials(); err != nil {
    45  		return nil, err
    46  	}
    47  	return &awsGeneratorType{
    48  			targets: awsutil.TargetList{awsutil.Target{args[1], args[0]}}},
    49  		nil
    50  }
    51  
    52  func newAwsFilteredGenerator(args []string,
    53  	logger log.DebugLogger) (generator, error) {
    54  	if err := loadCredentials(); err != nil {
    55  		return nil, err
    56  	}
    57  	gen := awsGeneratorType{
    58  		filterTagsFile: args[1],
    59  	}
    60  	if err := gen.targets.Set(args[0]); err != nil {
    61  		return nil, err
    62  	}
    63  	return &gen, nil
    64  }
    65  
    66  func newAwsLocalGenerator(args []string,
    67  	logger log.DebugLogger) (generator, error) {
    68  	if err := loadCredentials(); err != nil {
    69  		return nil, err
    70  	}
    71  	region, err := awsutil.GetLocalRegion()
    72  	if err != nil {
    73  		return nil, err
    74  	}
    75  	return &awsGeneratorType{
    76  			targets: awsutil.TargetList{awsutil.Target{"", region}}},
    77  		nil
    78  }
    79  
    80  func (g *awsGeneratorType) Generate(unused_datacentre string,
    81  	logger log.Logger) (*mdb.Mdb, error) {
    82  	resultsChannel := make(chan resultType, 1)
    83  	numTargets, err := credentialsStore.ForEachEC2Target(g.targets, nil,
    84  		func(awsService *ec2.EC2, account, region string, logger log.Logger) {
    85  			var result resultType
    86  			result.mdb, result.err = g.generateForTarget(awsService, account,
    87  				region, logger)
    88  			resultsChannel <- result
    89  		},
    90  		false, logger)
    91  	// Collect results.
    92  	var newMdb mdb.Mdb
    93  	hostnames := make(map[string]struct{})
    94  	for i := 0; i < numTargets; i++ {
    95  		result := <-resultsChannel
    96  		if result.err != nil {
    97  			if err == nil {
    98  				err = result.err
    99  				logger.Println(err)
   100  			}
   101  			continue
   102  		}
   103  		for _, machine := range result.mdb.Machines {
   104  			if _, ok := hostnames[machine.Hostname]; ok {
   105  				txt := "duplicate hostname: " + machine.Hostname
   106  				logger.Println(txt)
   107  				if err == nil {
   108  					err = errors.New(txt)
   109  				}
   110  				break
   111  			}
   112  			newMdb.Machines = append(newMdb.Machines, machine)
   113  		}
   114  	}
   115  	return &newMdb, err
   116  }
   117  
   118  func (g *awsGeneratorType) generateForTarget(svc *ec2.EC2, accountName string,
   119  	region string, logger log.Logger) (
   120  	*mdb.Mdb, error) {
   121  	filters, err := g.makeFilters()
   122  	if err != nil {
   123  		return nil, err
   124  	}
   125  	resp, err := svc.DescribeInstances(&ec2.DescribeInstancesInput{
   126  		Filters: filters,
   127  	})
   128  	if err != nil {
   129  		return nil, err
   130  	}
   131  	return extractMdb(resp, accountName, region), nil
   132  }
   133  
   134  func (g *awsGeneratorType) makeFilters() ([]*ec2.Filter, error) {
   135  	filters := make([]*ec2.Filter, 1, 1)
   136  	filters[0] = &ec2.Filter{
   137  		Name:   aws.String("instance-state-name"),
   138  		Values: []*string{aws.String(ec2.InstanceStateNameRunning)},
   139  	}
   140  	if g.filterTagsFile == "" {
   141  		return filters, nil
   142  	}
   143  	var tags []tagFilterType
   144  	if err := libjson.ReadFromFile(g.filterTagsFile, &tags); err != nil {
   145  		return nil, fmt.Errorf("error loading tags file: %s", err)
   146  	}
   147  	for _, tag := range tags {
   148  		filters = append(filters, &ec2.Filter{
   149  			Name:   aws.String("tag:" + tag.Key),
   150  			Values: aws.StringSlice(tag.Values),
   151  		})
   152  	}
   153  	return filters, nil
   154  }
   155  
   156  func extractMdb(output *ec2.DescribeInstancesOutput, accountName string,
   157  	region string) *mdb.Mdb {
   158  	var result mdb.Mdb
   159  	for _, reservation := range output.Reservations {
   160  		accountId := aws.StringValue(reservation.OwnerId)
   161  		for _, instance := range reservation.Instances {
   162  			if instance.PrivateDnsName != nil {
   163  				machine := mdb.Machine{
   164  					Hostname: *instance.PrivateDnsName,
   165  					AwsMetadata: &mdb.AwsMetadata{
   166  						AccountId:   accountId,
   167  						AccountName: accountName,
   168  						InstanceId:  *instance.InstanceId,
   169  						Region:      region,
   170  						Tags:        make(map[string]string),
   171  					},
   172  				}
   173  				if instance.PrivateIpAddress != nil {
   174  					machine.IpAddress = *instance.PrivateIpAddress
   175  				}
   176  				extractTags(instance.Tags, &machine)
   177  				result.Machines = append(result.Machines, machine)
   178  			}
   179  		}
   180  	}
   181  	return &result
   182  }
   183  
   184  func extractTags(tags []*ec2.Tag, machine *mdb.Machine) {
   185  	for _, tag := range tags {
   186  		if tag.Key == nil || tag.Value == nil {
   187  			continue
   188  		}
   189  		machine.AwsMetadata.Tags[*tag.Key] = *tag.Value
   190  		switch *tag.Key {
   191  		case "RequiredImage":
   192  			machine.RequiredImage = *tag.Value
   193  		case "PlannedImage":
   194  			machine.PlannedImage = *tag.Value
   195  		case "DisableUpdates":
   196  			machine.DisableUpdates = true
   197  		case "OwnerGroup":
   198  			machine.OwnerGroup = *tag.Value
   199  		}
   200  	}
   201  }