github.com/hairyhenderson/templater@v3.5.0+incompatible/aws/ec2info.go (about)

     1  package aws
     2  
     3  import (
     4  	"net/http"
     5  	"os"
     6  	"strconv"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/aws/aws-sdk-go/aws"
    11  	"github.com/aws/aws-sdk-go/aws/session"
    12  	"github.com/aws/aws-sdk-go/service/ec2"
    13  	"github.com/pkg/errors"
    14  )
    15  
    16  var describerClient InstanceDescriber
    17  
    18  var (
    19  	co             ClientOptions
    20  	coInit         sync.Once
    21  	sdkSession     *session.Session
    22  	sdkSessionInit sync.Once
    23  )
    24  
    25  // ClientOptions -
    26  type ClientOptions struct {
    27  	Timeout time.Duration
    28  }
    29  
    30  // Ec2Info -
    31  type Ec2Info struct {
    32  	describer  func() (InstanceDescriber, error)
    33  	metaClient *Ec2Meta
    34  	cache      map[string]interface{}
    35  }
    36  
    37  // InstanceDescriber - A subset of ec2iface.EC2API that we can use to call EC2.DescribeInstances
    38  type InstanceDescriber interface {
    39  	DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
    40  }
    41  
    42  // GetClientOptions - Centralised reading of AWS_TIMEOUT
    43  // ... but cannot use in vault/auth.go as different strconv.Atoi error handling
    44  func GetClientOptions() ClientOptions {
    45  	coInit.Do(func() {
    46  		timeout := os.Getenv("AWS_TIMEOUT")
    47  		if timeout == "" {
    48  			timeout = "500"
    49  		}
    50  
    51  		t, err := strconv.Atoi(timeout)
    52  		if err != nil {
    53  			panic(errors.Wrapf(err, "Invalid AWS_TIMEOUT value '%s' - must be an integer\n", timeout))
    54  		}
    55  
    56  		co.Timeout = time.Duration(t) * time.Millisecond
    57  	})
    58  	return co
    59  }
    60  
    61  // SDKSession -
    62  func SDKSession(region ...string) *session.Session {
    63  	sdkSessionInit.Do(func() {
    64  		options := GetClientOptions()
    65  		timeout := options.Timeout
    66  		if timeout == 0 {
    67  			timeout = 500 * time.Millisecond
    68  		}
    69  
    70  		config := aws.NewConfig()
    71  		config = config.WithHTTPClient(&http.Client{Timeout: timeout})
    72  
    73  		metaRegion := ""
    74  		if len(region) > 0 {
    75  			metaRegion = region[0]
    76  		} else {
    77  			var err error
    78  			metaRegion, err = getRegion()
    79  			if err != nil {
    80  				panic(errors.Wrap(err, "failed to determine EC2 region"))
    81  			}
    82  		}
    83  		config = config.WithRegion(metaRegion)
    84  
    85  		sdkSession = session.Must(session.NewSessionWithOptions(session.Options{
    86  			Config:            *config,
    87  			SharedConfigState: session.SharedConfigEnable,
    88  		}))
    89  	})
    90  	return sdkSession
    91  }
    92  
    93  // Attempts to get the EC2 region to use. If we're running on an EC2 Instance
    94  // and neither AWS_REGION nor AWS_DEFAULT_REGION are set, we'll infer from EC2
    95  // metadata.
    96  // Once https://github.com/aws/aws-sdk-go/issues/1103 is resolve this should be
    97  // tidier!
    98  func getRegion(m ...*Ec2Meta) (string, error) {
    99  	region := ""
   100  	_, default1 := os.LookupEnv("AWS_REGION")
   101  	_, default2 := os.LookupEnv("AWS_DEFAULT_REGION")
   102  	if !default1 && !default2 {
   103  		// Maybe we're in EC2, let's try to read metadata
   104  		var metaClient *Ec2Meta
   105  		if len(m) > 0 {
   106  			metaClient = m[0]
   107  		} else {
   108  			metaClient = NewEc2Meta(GetClientOptions())
   109  		}
   110  		var err error
   111  		region, err = metaClient.Region()
   112  		if err != nil {
   113  			return "", errors.Wrap(err, "failed to determine EC2 region")
   114  		}
   115  	}
   116  	return region, nil
   117  }
   118  
   119  // NewEc2Info -
   120  func NewEc2Info(options ClientOptions) (info *Ec2Info) {
   121  	metaClient := NewEc2Meta(options)
   122  	return &Ec2Info{
   123  		describer: func() (InstanceDescriber, error) {
   124  			if describerClient == nil {
   125  				session := SDKSession()
   126  				describerClient = ec2.New(session)
   127  			}
   128  			return describerClient, nil
   129  		},
   130  		metaClient: metaClient,
   131  		cache:      make(map[string]interface{}),
   132  	}
   133  }
   134  
   135  // Tag -
   136  func (e *Ec2Info) Tag(tag string, def ...string) (string, error) {
   137  	output, err := e.describeInstance()
   138  	if err != nil {
   139  		return "", err
   140  	}
   141  	if output == nil {
   142  		return returnDefault(def), nil
   143  	}
   144  
   145  	if len(output.Reservations) > 0 &&
   146  		len(output.Reservations[0].Instances) > 0 &&
   147  		len(output.Reservations[0].Instances[0].Tags) > 0 {
   148  		for _, v := range output.Reservations[0].Instances[0].Tags {
   149  			if *v.Key == tag {
   150  				return *v.Value, nil
   151  			}
   152  		}
   153  	}
   154  
   155  	return returnDefault(def), nil
   156  }
   157  
   158  func (e *Ec2Info) describeInstance() (output *ec2.DescribeInstancesOutput, err error) {
   159  	// cache the InstanceDescriber here
   160  	d, err := e.describer()
   161  	if err != nil || e.metaClient.nonAWS {
   162  		return nil, err
   163  	}
   164  
   165  	if cached, ok := e.cache["DescribeInstances"]; ok {
   166  		output = cached.(*ec2.DescribeInstancesOutput)
   167  	} else {
   168  		instanceID, err := e.metaClient.Meta("instance-id")
   169  		if err != nil {
   170  			return nil, err
   171  		}
   172  		input := &ec2.DescribeInstancesInput{
   173  			InstanceIds: aws.StringSlice([]string{instanceID}),
   174  		}
   175  
   176  		output, err = d.DescribeInstances(input)
   177  		if err != nil {
   178  			// default to nil if we can't describe the instance - this could be for any reason
   179  			return nil, nil
   180  		}
   181  		e.cache["DescribeInstances"] = output
   182  	}
   183  	return output, nil
   184  }