forked from Technofy/cloudwatch_exporter
/
sessions.go
208 lines (183 loc) · 6 KB
/
sessions.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
package sessions
import (
"fmt"
"net/http"
"os"
"text/tabwriter"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/rds"
"github.com/prometheus/common/log"
"github.com/percona/rds_exporter/config"
)
// Instance represents a single RDS instance information in runtime.
type Instance struct {
Region string
Instance string
DisableBasicMetrics bool
DisableEnhancedMetrics bool
ResourceID string
Labels map[string]string
EnhancedMonitoringInterval time.Duration
}
func (i Instance) String() string {
res := i.Region + "/" + i.Instance
if i.ResourceID != "" {
res += " (" + i.ResourceID + ")"
}
return res
}
// Sessions is a pool of AWS sessions.
type Sessions struct {
sessions map[*session.Session][]Instance
}
// New creates a new sessions pool for given configuration.
func New(instances []config.Instance, client *http.Client, trace bool) (*Sessions, error) {
logger := log.With("component", "sessions")
logger.Info("Creating sessions...")
res := &Sessions{
sessions: make(map[*session.Session][]Instance),
}
sharedSessions := make(map[string]*session.Session) // region/key => session
for _, instance := range instances {
// re-use session for the same region and key (explicit or empty for implicit) pair
if s := sharedSessions[instance.Region+"/"+instance.AWSAccessKey]; s != nil {
res.sessions[s] = append(res.sessions[s], Instance{
Region: instance.Region,
Instance: instance.Instance,
Labels: instance.Labels,
DisableBasicMetrics: instance.DisableBasicMetrics,
DisableEnhancedMetrics: instance.DisableEnhancedMetrics,
})
continue
}
// use given credentials, or default credential chain
var creds *credentials.Credentials
creds, err := buildCredentials(instance)
if err != nil {
return nil, err
}
// make config with careful logging
awsCfg := &aws.Config{
Credentials: creds,
Region: aws.String(instance.Region),
HTTPClient: client,
}
if trace {
// fail-safe
if _, ok := os.LookupEnv("CI"); ok {
panic("Do not enable AWS request tracing on CI - output will contain credentials.")
}
awsCfg.Logger = aws.LoggerFunc(logger.Debug)
awsCfg.CredentialsChainVerboseErrors = aws.Bool(true)
level := aws.LogDebugWithSigning | aws.LogDebugWithHTTPBody
level |= aws.LogDebugWithRequestRetries | aws.LogDebugWithRequestErrors | aws.LogDebugWithEventStreamBody
awsCfg.LogLevel = aws.LogLevel(level)
}
// store session
s, err := session.NewSession(awsCfg)
if err != nil {
return nil, err
}
sharedSessions[instance.Region+"/"+instance.AWSAccessKey] = s
res.sessions[s] = append(res.sessions[s], Instance{
Region: instance.Region,
Instance: instance.Instance,
Labels: instance.Labels,
DisableBasicMetrics: instance.DisableBasicMetrics,
DisableEnhancedMetrics: instance.DisableEnhancedMetrics,
})
}
// add resource ID to all instances
for session, instances := range res.sessions {
svc := rds.New(session)
var marker *string
for {
output, err := svc.DescribeDBInstances(&rds.DescribeDBInstancesInput{
Marker: marker,
})
if err != nil {
logger.Errorf("Failed to get resource IDs: %s.", err)
break
}
for _, dbInstance := range output.DBInstances {
for i, instance := range instances {
if *dbInstance.DBInstanceIdentifier == instance.Instance {
instances[i].ResourceID = *dbInstance.DbiResourceId
instances[i].EnhancedMonitoringInterval = time.Duration(*dbInstance.MonitoringInterval) * time.Second
}
}
}
if marker = output.Marker; marker == nil {
break
}
}
}
// remove instances without resource ID
for session, instances := range res.sessions {
newInstances := make([]Instance, 0, len(instances))
for _, instance := range instances {
if instance.ResourceID == "" {
logger.Errorf("Skipping %s - can't determine resourceID.", instance)
continue
}
newInstances = append(newInstances, instance)
}
res.sessions[session] = newInstances
}
// remove sessions without instances
for _, s := range sharedSessions {
if len(res.sessions[s]) == 0 {
delete(res.sessions, s)
}
}
w := tabwriter.NewWriter(os.Stderr, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "Region\tInstance\tResource ID\tInterval\n")
for _, instances := range res.sessions {
for _, instance := range instances {
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", instance.Region, instance.Instance, instance.ResourceID, instance.EnhancedMonitoringInterval)
}
}
_ = w.Flush()
logger.Infof("Using %d sessions.", len(res.sessions))
return res, nil
}
// GetSession returns session and full instance information for given region and instance.
func (s *Sessions) GetSession(region, instance string) (*session.Session, *Instance) {
for session, instances := range s.sessions {
for _, i := range instances {
if i.Region == region && i.Instance == instance {
return session, &i
}
}
}
return nil, nil
}
func buildCredentials(instance config.Instance) (*credentials.Credentials, error) {
if instance.AWSRoleArn != "" {
stsSession, err := session.NewSession(&aws.Config{
Region: aws.String(instance.Region),
Credentials: credentials.NewStaticCredentials(instance.AWSAccessKey, instance.AWSSecretKey, ""),
})
if err != nil {
return nil, err
}
return stscreds.NewCredentials(stsSession, instance.AWSRoleArn), nil
}
if instance.AWSAccessKey != "" || instance.AWSSecretKey != "" {
return credentials.NewCredentials(&credentials.StaticProvider{
Value: credentials.Value{
AccessKeyID: instance.AWSAccessKey,
SecretAccessKey: instance.AWSSecretKey,
},
}), nil
}
return nil, nil
}
// AllSessions returns all sessions and instances.
func (s *Sessions) AllSessions() map[*session.Session][]Instance {
return s.sessions
}