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 }