text: package aws
  
  import (
  	"net/http"
  	"os"
  	"strconv"
  	"sync"
  	"time"
  
  	"github.com/aws/aws-sdk-go/aws"
  	"github.com/aws/aws-sdk-go/aws/credentials"
  	"github.com/aws/aws-sdk-go/aws/session"
  	"github.com/aws/aws-sdk-go/service/ec2"
  	"github.com/pkg/errors"
  )
  
  var describerClient InstanceDescriber
  
  var (
  	co             ClientOptions
  	coInit         sync.Once
  	sdkSession     *session.Session
  	sdkSessionInit sync.Once
  )
  
  // ClientOptions -
  type ClientOptions struct {
  	Timeout time.Duration
  }
  
  // Ec2Info -
  type Ec2Info struct {
  	describer  func() (InstanceDescriber, error)
  	metaClient *Ec2Meta
  	cache      map[string]interface{}
  }
  
  // InstanceDescriber - A subset of ec2iface.EC2API that we can use to call EC2.DescribeInstances
  type InstanceDescriber interface {
  	DescribeInstances(*ec2.DescribeInstancesInput) (*ec2.DescribeInstancesOutput, error)
  }
  
  // GetClientOptions - Centralised reading of AWS_TIMEOUT
  // ... but cannot use in vault/auth.go as different strconv.Atoi error handling
  func GetClientOptions() ClientOptions {
  	coInit.Do(func() {
  		timeout := os.Getenv("AWS_TIMEOUT")
  		if timeout == "" {
  			timeout = "500"
  		}
  
  		t, err := strconv.Atoi(timeout)
  		if err != nil {
  			panic(errors.Wrapf(err, "Invalid AWS_TIMEOUT value '%s' - must be an integer\n", timeout))
  		}
  
  		co.Timeout = time.Duration(t) * time.Millisecond
  	})
  	return co
  }
  
  // SDKSession -
  func SDKSession(region ...string) *session.Session {
  	sdkSessionInit.Do(func() {
  		options := GetClientOptions()
  		timeout := options.Timeout
  		if timeout == 0 {
  			timeout = 500 * time.Millisecond
  		}
  
  		config := aws.NewConfig()
  		config = config.WithHTTPClient(&http.Client{Timeout: timeout})
  
  		if os.Getenv("AWS_ANON") == "true" {
  			config = config.WithCredentials(credentials.AnonymousCredentials)
  		}
  
  		metaRegion := ""
  		if len(region) > 0 {
  			metaRegion = region[0]
  		} else {
  			var err error
  			metaRegion, err = getRegion()
  			if err != nil {
  				panic(errors.Wrap(err, "failed to determine EC2 region"))
  			}
  		}
  		if metaRegion != "" && metaRegion != unknown {
  			config = config.WithRegion(metaRegion)
  		}
  		config = config.WithCredentialsChainVerboseErrors(true)
  
  		sdkSession = session.Must(session.NewSessionWithOptions(session.Options{
  			Config:            *config,
  			SharedConfigState: session.SharedConfigEnable,
  		}))
  	})
  	return sdkSession
  }
  
  // Attempts to get the EC2 region to use. If we're running on an EC2 Instance
  // and neither AWS_REGION nor AWS_DEFAULT_REGION are set, we'll infer from EC2
  // metadata.
  // Once https://github.com/aws/aws-sdk-go/issues/1103 is resolve this should be
  // tidier!
  func getRegion(m ...*Ec2Meta) (string, error) {
  	region := ""
  	_, default1 := os.LookupEnv("AWS_REGION")
  	_, default2 := os.LookupEnv("AWS_DEFAULT_REGION")
  	if !default1 && !default2 {
  		// Maybe we're in EC2, let's try to read metadata
  		var metaClient *Ec2Meta
  		if len(m) > 0 {
  			metaClient = m[0]
  		} else {
  			metaClient = NewEc2Meta(GetClientOptions())
  		}
  		var err error
  		region, err = metaClient.Region()
  		if err != nil {
  			return "", errors.Wrap(err, "failed to determine EC2 region")
  		}
  	}
  	return region, nil
  }
  
  // NewEc2Info -
  func NewEc2Info(options ClientOptions) (info *Ec2Info) {
  	metaClient := NewEc2Meta(options)
  	return &Ec2Info{
  		describer: func() (InstanceDescriber, error) {
  			if describerClient == nil {
  				session := SDKSession()
  				describerClient = ec2.New(session)
  			}
  			return describerClient, nil
  		},
  		metaClient: metaClient,
  		cache:      make(map[string]interface{}),
  	}
  }
  
  // Tag -
  func (e *Ec2Info) Tag(tag string, def ...string) (string, error) {
  	output, err := e.describeInstance()
  	if err != nil {
  		return "", err
  	}
  	if output == nil {
  		return returnDefault(def), nil
  	}
  
  	if len(output.Reservations) > 0 &&
  		len(output.Reservations[0].Instances) > 0 &&
  		len(output.Reservations[0].Instances[0].Tags) > 0 {
  		for _, v := range output.Reservations[0].Instances[0].Tags {
  			if *v.Key == tag {
  				return *v.Value, nil
  			}
  		}
  	}
  
  	return returnDefault(def), nil
  }
  
  func (e *Ec2Info) Tags() (map[string]string, error) {
  	tags := map[string]string{}
  
  	output, err := e.describeInstance()
  	if err != nil {
  		return tags, err
  	}
  	if output == nil {
  		return tags, nil
  	}
  
  	if len(output.Reservations) > 0 &&
  		len(output.Reservations[0].Instances) > 0 &&
  		len(output.Reservations[0].Instances[0].Tags) > 0 {
  		for _, v := range output.Reservations[0].Instances[0].Tags {
  			tags[*v.Key] = *v.Value
  		}
  	}
  
  	return tags, nil
  }
  
  func (e *Ec2Info) describeInstance() (output *ec2.DescribeInstancesOutput, err error) {
  	// cache the InstanceDescriber here
  	d, err := e.describer()
  	if err != nil || e.metaClient.nonAWS {
  		return nil, err
  	}
  
  	if cached, ok := e.cache["DescribeInstances"]; ok {
  		output = cached.(*ec2.DescribeInstancesOutput)
  	} else {
  		instanceID, err := e.metaClient.Meta("instance-id")
  		if err != nil {
  			return nil, err
  		}
  		input := &ec2.DescribeInstancesInput{
  			InstanceIds: aws.StringSlice([]string{instanceID}),
  		}
  
  		output, err = d.DescribeInstances(input)
  		if err != nil {
  			// default to nil if we can't describe the instance - this could be for any reason
  			return nil, nil
  		}
  		e.cache["DescribeInstances"] = output
  	}
  	return output, nil
  }
  