Managing multiple AWS accounts can sometimes be tough, even when doing something as simple as matching a private IP address with a hostname.
In the past I used the aws cli tools, but I had to constantly switch both the accounts and the regions when making requests:
As you can imagine this is extremely time consuming, even when using the CLI tools. I wrote a small tool that will find every single instance you can view using every account available to you (according to your ~/.aws/config). The aggregate results can then be searched.
I choose to use the existing ~/.aws/config file so that it works along side your normal aws cli tools.
This is a very good use-case for Golang, which has nice concurrency primitives. With five accounts this script (once compiled) will display all of the results in under 1.5 seconds. Not bad.
The result is a space-separated list with some additional values added. It should be easy to find what you're looking for:
The output is a space-separated file and only "running" and "pending" instances are displayed. For a list of filters or instance attributes consult the official documentation.
In the past I used the aws cli tools, but I had to constantly switch both the accounts and the regions when making requests:
# Make it's in the prod-5 account in us-west-1
aws ec2 --profile=prod-5 --region=us-west-1 | grep my_instance
# Okay not that account or region, let's try eu-west-1
aws ec2 --profile=prod-5 --region=eu-west-2 | grep my_instance
Repeat 1x for each account and region
As you can imagine this is extremely time consuming, even when using the CLI tools. I wrote a small tool that will find every single instance you can view using every account available to you (according to your ~/.aws/config). The aggregate results can then be searched.
I choose to use the existing ~/.aws/config file so that it works along side your normal aws cli tools.
This is a very good use-case for Golang, which has nice concurrency primitives. With five accounts this script (once compiled) will display all of the results in under 1.5 seconds. Not bad.
The result is a space-separated list with some additional values added. It should be easy to find what you're looking for:
$ ./aws_list
i-71930187 prod-manage005 10.0.0.180 t2.micro 207.171.166.22 prod-account
i-71930187 stage-controller7 10.0.0.164 t2.medium 72.21.206.80 staging-account
i-71930187 prod-vpn01 10.0.0.239 m4.large None prod-account
i-71930187 stephen-test-host 10.0.0.216 m3.large 216.58.216.174 test-account
...
The output is a space-separated file and only "running" and "pending" instances are displayed. For a list of filters or instance attributes consult the official documentation.
package main import ( "fmt" "github.com/awslabs/aws-sdk-go/aws" "github.com/awslabs/aws-sdk-go/service/ec2" "github.com/vaughan0/go-ini" "net/url" "os" "runtime" "strings" "sync" ) func check(e error) { if e != nil { panic(e) } } /* printIds accepts an aws credentials file and a region, and prints out all instances within the region in a format that's acceptable to us. Currently that format is like this: instance_id name private_ip instance_type public_ip account Any values that aren't available (such as public ip) will be printed out as "None" Because the "name" parameter is user-defined, we'll run QueryEscape on it so that our output stays as a space-separated line. */ func printIds(creds aws.CredentialsProvider, account string, region string, wg *sync.WaitGroup) { defer wg.Done() svc := ec2.New(&aws.Config{ Credentials: creds, Region: region, }) // Here we create an input that will filter any instances that aren't either // of these two states. This is generally what we want params := &ec2.DescribeInstancesInput{ Filters: []*ec2.Filter{ &ec2.Filter{ Name: aws.String("instance-state-name"), Values: []*string{ aws.String("running"), aws.String("pending"), }, }, }, } // TODO: Actually care if we can't connect to a host resp, _ := svc.DescribeInstances(params) // if err != nil { // panic(err) // } // Loop through the instances. They don't always have a name-tag so set it // to None if we can't find anything. for idx, _ := range resp.Reservations { for _, inst := range resp.Reservations[idx].Instances { // We need to see if the Name is one of the tags. It's not always // present and not required in Ec2. name := "None" for _, keys := range inst.Tags { if *keys.Key == "Name" { name = url.QueryEscape(*keys.Value) } } important_vals := []*string{ inst.InstanceID, &name, inst.PrivateIPAddress, inst.InstanceType, inst.PublicIPAddress, &account, } // Convert any nil value to a printable string in case it doesn't // doesn't exist, which is the case with certain values output_vals := []string{} for _, val := range important_vals { if val != nil { output_vals = append(output_vals, *val) } else { output_vals = append(output_vals, "None") } } // The values that we care about, in the order we want to print them fmt.Println(strings.Join(output_vals, " ")) } } } func main() { // Go for it! runtime.GOMAXPROCS(runtime.NumCPU()) // Make sure the config file exists config := os.Getenv("HOME") + "/.aws/config" if _, err := os.Stat(config); os.IsNotExist(err) { fmt.Println("No config file found at: %s", config) os.Exit(1) } var wg sync.WaitGroup file, err := ini.LoadFile(config) check(err) for key, values := range file { profile := strings.Fields(key) // Don't print the default or non-standard profiles if len(profile) != 2 { continue } // Where to find this host. The account isn't necessary for the creds // but it's something we expose to users when we print account := profile[1] key := values["aws_access_key_id"] pass := values["aws_secret_access_key"] creds := aws.Creds(key, pass, "") // Gather a list of all available AWS regions. Even though we're gathering // all regions, we still must use a region here for api calls. svc := ec2.New(&aws.Config{ Credentials: creds, Region: "us-west-1", }) // Iterate over every single stinking region to get a list of available // ec2 instances regions, err := svc.DescribeRegions(&ec2.DescribeRegionsInput{}) check(err) for _, region := range regions.Regions { wg.Add(1) go printIds(creds, account, *region.RegionName, &wg) } } // Allow the goroutines to finish printing wg.Wait() }
Excellent, thank you. I'm rapidly going through the sequence:
ReplyDeleteconsole-> cli -> bash script+CLI -> ruby script +cli -> go.
Go is almost designed for this, it seems to me, which is ironic as AWS is the market leader which GCP is trying to chase down.
Anyway, thanks for the kickstart.