Upgrade AWS SDK to v2

AWS SDK v1 is end of life soon, so migrate to the V2 SDK. The credential loading should work more consistently with other projects that use the SDK and load credentials from the appropriate locations including from environment variables. This affects the EC2 and Lightsail service discovery features.

Signed-off-by: Joe Adams <github@joeadams.io>
This commit is contained in:
Joe Adams
2025-07-29 23:06:05 -04:00
parent 13ed544426
commit eab9b696f2
5 changed files with 204 additions and 186 deletions

View File

@@ -23,14 +23,15 @@ import (
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go-v2/aws"
awsConfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@@ -124,17 +125,28 @@ func (c *EC2SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return err
}
if c.Region == "" {
sess, err := session.NewSession()
// TODO(@sysadmind): Should we get a context from somewhere?
ctx := context.TODO()
cfg, err := awsConfig.LoadDefaultConfig(ctx)
if err != nil {
return err
}
metadata := ec2metadata.New(sess)
region, err := metadata.Region()
if err != nil {
return errors.New("EC2 SD configuration requires a region")
if cfg.Region != "" {
// If the region is already set in the config, use it.
// This can happen if the user has set the region in the AWS config file or environment variables.
c.Region = cfg.Region
} else {
// Attempt to get the region from the instance metadata service.
metaClient := imds.NewFromConfig(cfg)
result, err := metaClient.GetRegion(ctx, &imds.GetRegionInput{})
if err != nil {
return errors.New("EC2 SD configuration requires a region")
}
c.Region = result.Region
}
c.Region = region
}
for _, f := range c.Filters {
if len(f.Values) == 0 {
return errors.New("EC2 SD configuration filter values cannot be empty")
@@ -143,13 +155,18 @@ func (c *EC2SDConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
return c.HTTPClientConfig.Validate()
}
type ec2Client interface {
DescribeAvailabilityZones(ctx context.Context, params *ec2.DescribeAvailabilityZonesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error)
DescribeInstances(ctx context.Context, params *ec2.DescribeInstancesInput, optFns ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error)
}
// EC2Discovery periodically performs EC2-SD requests. It implements
// the Discoverer interface.
type EC2Discovery struct {
*refresh.Discovery
logger *slog.Logger
cfg *EC2SDConfig
ec2 ec2iface.EC2API
ec2 ec2Client
// azToAZID maps this account's availability zones to their underlying AZ
// ID, e.g. eu-west-2a -> euw2-az2. Refreshes are performed sequentially, so
@@ -183,46 +200,43 @@ func NewEC2Discovery(conf *EC2SDConfig, logger *slog.Logger, metrics discovery.D
return d, nil
}
func (d *EC2Discovery) ec2Client(context.Context) (ec2iface.EC2API, error) {
func (d *EC2Discovery) ec2Client(ctx context.Context) (ec2Client, error) {
if d.ec2 != nil {
return d.ec2, nil
}
credProvider := credentials.NewStaticCredentialsProvider(d.cfg.AccessKey, string(d.cfg.SecretKey), "")
creds := credentials.NewStaticCredentials(d.cfg.AccessKey, string(d.cfg.SecretKey), "")
if d.cfg.AccessKey == "" && d.cfg.SecretKey == "" {
creds = nil
}
client, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "ec2_sd")
// Build the HTTP client from the provided HTTPClientConfig.
httpClient, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "ec2_sd")
if err != nil {
return nil, err
}
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Endpoint: &d.cfg.Endpoint,
Region: &d.cfg.Region,
Credentials: creds,
HTTPClient: client,
},
Profile: d.cfg.Profile,
})
// Build the AWS config with the provided region and credentials.
cfg, err := awsConfig.LoadDefaultConfig(
ctx,
awsConfig.WithRegion(d.cfg.Region),
awsConfig.WithCredentialsProvider(credProvider),
awsConfig.WithSharedConfigProfile(d.cfg.Profile),
awsConfig.WithHTTPClient(httpClient),
)
if err != nil {
return nil, fmt.Errorf("could not create aws session: %w", err)
return nil, fmt.Errorf("could not create aws config: %w", err)
}
// If the role ARN is set, assume the role to get credentials and set the credentials provider in the config.
if d.cfg.RoleARN != "" {
creds := stscreds.NewCredentials(sess, d.cfg.RoleARN)
d.ec2 = ec2.New(sess, &aws.Config{Credentials: creds})
} else {
d.ec2 = ec2.New(sess)
assumeProvider := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), d.cfg.RoleARN)
cfg.Credentials = aws.NewCredentialsCache(assumeProvider)
}
d.ec2 = ec2.NewFromConfig(cfg)
return d.ec2, nil
}
func (d *EC2Discovery) refreshAZIDs(ctx context.Context) error {
azs, err := d.ec2.DescribeAvailabilityZonesWithContext(ctx, &ec2.DescribeAvailabilityZonesInput{})
azs, err := d.ec2.DescribeAvailabilityZones(ctx, &ec2.DescribeAvailabilityZonesInput{})
if err != nil {
return err
}
@@ -243,11 +257,11 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
Source: d.cfg.Region,
}
var filters []*ec2.Filter
var filters []ec2Types.Filter
for _, f := range d.cfg.Filters {
filters = append(filters, &ec2.Filter{
filters = append(filters, ec2Types.Filter{
Name: aws.String(f.Name),
Values: aws.StringSlice(f.Values),
Values: f.Values,
})
}
@@ -262,7 +276,18 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
}
input := &ec2.DescribeInstancesInput{Filters: filters}
if err := ec2Client.DescribeInstancesPagesWithContext(ctx, input, func(p *ec2.DescribeInstancesOutput, _ bool) bool {
paginator := ec2.NewDescribeInstancesPaginator(ec2Client, input)
for paginator.HasMorePages() {
p, err := paginator.NextPage(ctx)
if err != nil {
var awsErr smithy.APIError
if errors.As(err, &awsErr) && (awsErr.ErrorCode() == "AuthFailure" || awsErr.ErrorCode() == "UnauthorizedOperation") {
d.ec2 = nil
}
return nil, fmt.Errorf("could not describe instances: %w", err)
}
for _, r := range p.Reservations {
for _, inst := range r.Instances {
if inst.PrivateIpAddress == nil {
@@ -285,8 +310,8 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
addr := net.JoinHostPort(*inst.PrivateIpAddress, strconv.Itoa(d.cfg.Port))
labels[model.AddressLabel] = model.LabelValue(addr)
if inst.Platform != nil {
labels[ec2LabelPlatform] = model.LabelValue(*inst.Platform)
if inst.Platform != "" {
labels[ec2LabelPlatform] = model.LabelValue(inst.Platform)
}
if inst.PublicIpAddress != nil {
@@ -302,15 +327,15 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
"az", *inst.Placement.AvailabilityZone)
}
labels[ec2LabelAZID] = model.LabelValue(azID)
labels[ec2LabelInstanceState] = model.LabelValue(*inst.State.Name)
labels[ec2LabelInstanceType] = model.LabelValue(*inst.InstanceType)
labels[ec2LabelInstanceState] = model.LabelValue(inst.State.Name)
labels[ec2LabelInstanceType] = model.LabelValue(inst.InstanceType)
if inst.InstanceLifecycle != nil {
labels[ec2LabelInstanceLifecycle] = model.LabelValue(*inst.InstanceLifecycle)
if inst.InstanceLifecycle != "" {
labels[ec2LabelInstanceLifecycle] = model.LabelValue(inst.InstanceLifecycle)
}
if inst.Architecture != nil {
labels[ec2LabelArch] = model.LabelValue(*inst.Architecture)
if inst.Architecture != "" {
labels[ec2LabelArch] = model.LabelValue(inst.Architecture)
}
if inst.VpcId != nil {
@@ -337,7 +362,7 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
// we might have to extend the slice with more than one element
// that could leave empty strings in the list which is intentional
// to keep the position/device index information
for int64(len(primaryipv6addrs)) <= *eni.Attachment.DeviceIndex {
for int32(len(primaryipv6addrs)) <= *eni.Attachment.DeviceIndex {
primaryipv6addrs = append(primaryipv6addrs, "")
}
primaryipv6addrs[*eni.Attachment.DeviceIndex] = *ipv6addr.Ipv6Address
@@ -363,7 +388,7 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
}
for _, t := range inst.Tags {
if t == nil || t.Key == nil || t.Value == nil {
if t.Key == nil || t.Value == nil {
continue
}
name := strutil.SanitizeLabelName(*t.Key)
@@ -372,13 +397,7 @@ func (d *EC2Discovery) refresh(ctx context.Context) ([]*targetgroup.Group, error
tg.Targets = append(tg.Targets, labels)
}
}
return true
}); err != nil {
var awsErr awserr.Error
if errors.As(err, &awsErr) && (awsErr.Code() == "AuthFailure" || awsErr.Code() == "UnauthorizedOperation") {
d.ec2 = nil
}
return nil, fmt.Errorf("could not describe instances: %w", err)
}
return []*targetgroup.Group{tg}, nil
}

View File

@@ -18,10 +18,11 @@ import (
"errors"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/ec2"
ec2Types "github.com/aws/aws-sdk-go-v2/service/ec2/types"
"github.com/prometheus/common/model"
"github.com/stretchr/testify/require"
"go.uber.org/goleak"
@@ -39,7 +40,7 @@ func boolptr(b bool) *bool {
return &b
}
func int64ptr(i int64) *int64 {
func int32ptr(i int32) *int32 {
return &i
}
@@ -51,7 +52,7 @@ type ec2DataStore struct {
ownerID string
instances []*ec2.Instance
instances []ec2Types.Instance
}
// The tests itself.
@@ -121,7 +122,7 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
"azname-b": "azid-2",
"azname-c": "azid-3",
},
instances: []*ec2.Instance{
instances: []ec2Types.Instance{
{
InstanceId: strptr("instance-id-noprivateip"),
},
@@ -143,26 +144,26 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
"azname-c": "azid-3",
},
ownerID: "owner-id-novpc",
instances: []*ec2.Instance{
instances: []ec2Types.Instance{
{
// set every possible options and test them here
Architecture: strptr("architecture-novpc"),
Architecture: "architecture-novpc",
ImageId: strptr("ami-novpc"),
InstanceId: strptr("instance-id-novpc"),
InstanceLifecycle: strptr("instance-lifecycle-novpc"),
InstanceType: strptr("instance-type-novpc"),
Placement: &ec2.Placement{AvailabilityZone: strptr("azname-b")},
Platform: strptr("platform-novpc"),
InstanceLifecycle: "instance-lifecycle-novpc",
InstanceType: "instance-type-novpc",
Placement: &ec2Types.Placement{AvailabilityZone: strptr("azname-b")},
Platform: "platform-novpc",
PrivateDnsName: strptr("private-dns-novpc"),
PrivateIpAddress: strptr("1.2.3.4"),
PublicDnsName: strptr("public-dns-novpc"),
PublicIpAddress: strptr("42.42.42.2"),
State: &ec2.InstanceState{Name: strptr("running")},
State: &ec2Types.InstanceState{Name: "running"},
// test tags once and for all
Tags: []*ec2.Tag{
Tags: []ec2Types.Tag{
{Key: strptr("tag-1-key"), Value: strptr("tag-1-value")},
{Key: strptr("tag-2-key"), Value: strptr("tag-2-value")},
nil,
{},
{Value: strptr("tag-4-value")},
{Key: strptr("tag-5-key")},
},
@@ -206,22 +207,22 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
"azname-b": "azid-2",
"azname-c": "azid-3",
},
instances: []*ec2.Instance{
instances: []ec2Types.Instance{
{
// just the minimum needed for the refresh work
ImageId: strptr("ami-ipv4"),
InstanceId: strptr("instance-id-ipv4"),
InstanceType: strptr("instance-type-ipv4"),
Placement: &ec2.Placement{AvailabilityZone: strptr("azname-c")},
InstanceType: "instance-type-ipv4",
Placement: &ec2Types.Placement{AvailabilityZone: strptr("azname-c")},
PrivateIpAddress: strptr("5.6.7.8"),
State: &ec2.InstanceState{Name: strptr("running")},
State: &ec2Types.InstanceState{Name: "running"},
SubnetId: strptr("azid-3"),
VpcId: strptr("vpc-ipv4"),
// network interfaces
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []ec2Types.InstanceNetworkInterface{
// interface without subnet -> should be ignored
{
Ipv6Addresses: []*ec2.InstanceIpv6Address{
Ipv6Addresses: []ec2Types.InstanceIpv6Address{
{
Ipv6Address: strptr("2001:db8:1::1"),
IsPrimaryIpv6: boolptr(true),
@@ -230,12 +231,12 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
},
// interface with subnet, no IPv6
{
Ipv6Addresses: []*ec2.InstanceIpv6Address{},
Ipv6Addresses: []ec2Types.InstanceIpv6Address{},
SubnetId: strptr("azid-3"),
},
// interface with another subnet, no IPv6
{
Ipv6Addresses: []*ec2.InstanceIpv6Address{},
Ipv6Addresses: []ec2Types.InstanceIpv6Address{},
SubnetId: strptr("azid-1"),
},
},
@@ -274,25 +275,25 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
"azname-b": "azid-2",
"azname-c": "azid-3",
},
instances: []*ec2.Instance{
instances: []ec2Types.Instance{
{
// just the minimum needed for the refresh work
ImageId: strptr("ami-ipv6"),
InstanceId: strptr("instance-id-ipv6"),
InstanceType: strptr("instance-type-ipv6"),
Placement: &ec2.Placement{AvailabilityZone: strptr("azname-b")},
InstanceType: "instance-type-ipv6",
Placement: &ec2Types.Placement{AvailabilityZone: strptr("azname-b")},
PrivateIpAddress: strptr("9.10.11.12"),
State: &ec2.InstanceState{Name: strptr("running")},
State: &ec2Types.InstanceState{Name: "running"},
SubnetId: strptr("azid-2"),
VpcId: strptr("vpc-ipv6"),
// network interfaces
NetworkInterfaces: []*ec2.InstanceNetworkInterface{
NetworkInterfaces: []ec2Types.InstanceNetworkInterface{
// interface without primary IPv6, index 2
{
Attachment: &ec2.InstanceNetworkInterfaceAttachment{
DeviceIndex: int64ptr(3),
Attachment: &ec2Types.InstanceNetworkInterfaceAttachment{
DeviceIndex: int32ptr(3),
},
Ipv6Addresses: []*ec2.InstanceIpv6Address{
Ipv6Addresses: []ec2Types.InstanceIpv6Address{
{
Ipv6Address: strptr("2001:db8:2::1:1"),
IsPrimaryIpv6: boolptr(false),
@@ -302,10 +303,10 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
},
// interface with primary IPv6, index 1
{
Attachment: &ec2.InstanceNetworkInterfaceAttachment{
DeviceIndex: int64ptr(1),
Attachment: &ec2Types.InstanceNetworkInterfaceAttachment{
DeviceIndex: int32ptr(1),
},
Ipv6Addresses: []*ec2.InstanceIpv6Address{
Ipv6Addresses: []ec2Types.InstanceIpv6Address{
{
Ipv6Address: strptr("2001:db8:2::2:1"),
IsPrimaryIpv6: boolptr(false),
@@ -319,10 +320,10 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
},
// interface with primary IPv6, index 3
{
Attachment: &ec2.InstanceNetworkInterfaceAttachment{
DeviceIndex: int64ptr(3),
Attachment: &ec2Types.InstanceNetworkInterfaceAttachment{
DeviceIndex: int32ptr(3),
},
Ipv6Addresses: []*ec2.InstanceIpv6Address{
Ipv6Addresses: []ec2Types.InstanceIpv6Address{
{
Ipv6Address: strptr("2001:db8:2::3:1"),
IsPrimaryIpv6: boolptr(true),
@@ -332,10 +333,10 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
},
// interface without primary IPv6, index 0
{
Attachment: &ec2.InstanceNetworkInterfaceAttachment{
DeviceIndex: int64ptr(0),
Attachment: &ec2Types.InstanceNetworkInterfaceAttachment{
DeviceIndex: int32ptr(0),
},
Ipv6Addresses: []*ec2.InstanceIpv6Address{},
Ipv6Addresses: []ec2Types.InstanceIpv6Address{},
SubnetId: strptr("azid-3"),
},
},
@@ -388,7 +389,6 @@ func TestEC2DiscoveryRefresh(t *testing.T) {
// EC2 client mock.
type mockEC2Client struct {
ec2iface.EC2API
ec2Data ec2DataStore
}
@@ -399,16 +399,16 @@ func newMockEC2Client(ec2Data *ec2DataStore) *mockEC2Client {
return &client
}
func (m *mockEC2Client) DescribeAvailabilityZonesWithContext(_ aws.Context, _ *ec2.DescribeAvailabilityZonesInput, _ ...request.Option) (*ec2.DescribeAvailabilityZonesOutput, error) {
func (m *mockEC2Client) DescribeAvailabilityZones(_ context.Context, _ *ec2.DescribeAvailabilityZonesInput, _ ...func(*ec2.Options)) (*ec2.DescribeAvailabilityZonesOutput, error) {
if len(m.ec2Data.azToAZID) == 0 {
return nil, errors.New("No AZs found")
}
azs := make([]*ec2.AvailabilityZone, len(m.ec2Data.azToAZID))
azs := make([]ec2Types.AvailabilityZone, len(m.ec2Data.azToAZID))
i := 0
for k, v := range m.ec2Data.azToAZID {
azs[i] = &ec2.AvailabilityZone{
azs[i] = ec2Types.AvailabilityZone{
ZoneName: strptr(k),
ZoneId: strptr(v),
}
@@ -420,15 +420,13 @@ func (m *mockEC2Client) DescribeAvailabilityZonesWithContext(_ aws.Context, _ *e
}, nil
}
func (m *mockEC2Client) DescribeInstancesPagesWithContext(_ aws.Context, _ *ec2.DescribeInstancesInput, fn func(*ec2.DescribeInstancesOutput, bool) bool, _ ...request.Option) error {
r := ec2.Reservation{}
r.SetInstances(m.ec2Data.instances)
r.SetOwnerId(m.ec2Data.ownerID)
func (m *mockEC2Client) DescribeInstances(_ context.Context, _ *ec2.DescribeInstancesInput, _ ...func(*ec2.Options)) (*ec2.DescribeInstancesOutput, error) {
r := ec2Types.Reservation{}
r.Instances = append(r.Instances, m.ec2Data.instances...)
r.OwnerId = aws.String(m.ec2Data.ownerID)
o := ec2.DescribeInstancesOutput{}
o.SetReservations([]*ec2.Reservation{&r})
o.Reservations = []ec2Types.Reservation{r}
_ = fn(&o, true)
return nil
return &o, nil
}

View File

@@ -23,13 +23,14 @@ import (
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/stscreds"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/lightsail"
"github.com/aws/aws-sdk-go-v2/aws"
awsConfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/lightsail"
"github.com/aws/aws-sdk-go-v2/service/sts"
"github.com/aws/smithy-go"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/common/config"
"github.com/prometheus/common/model"
@@ -106,19 +107,26 @@ func (c *LightsailSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
return err
}
if c.Region == "" {
sess, err := session.NewSession()
// TODO(@sysadmind): Should we get a context from somewhere?
ctx := context.TODO()
cfg, err := awsConfig.LoadDefaultConfig(ctx)
if err != nil {
return err
}
metadata := ec2metadata.New(sess)
region, err := metadata.Region()
if err != nil {
//nolint:staticcheck // Capitalized first word.
return errors.New("Lightsail SD configuration requires a region")
if cfg.Region != "" {
// If the region is already set in the config, use it.
// This can happen if the user has set the region in the AWS config file or environment variables.
c.Region = cfg.Region
} else {
// Attempt to get the region from the instance metadata service.
metaClient := imds.NewFromConfig(cfg)
result, err := metaClient.GetRegion(ctx, &imds.GetRegionInput{})
if err != nil {
return errors.New("EC2 SD configuration requires a region")
}
c.Region = result.Region
}
c.Region = region
}
return c.HTTPClientConfig.Validate()
}
@@ -128,7 +136,7 @@ func (c *LightsailSDConfig) UnmarshalYAML(unmarshal func(interface{}) error) err
type LightsailDiscovery struct {
*refresh.Discovery
cfg *LightsailSDConfig
lightsail *lightsail.Lightsail
lightsail *lightsail.Client
}
// NewLightsailDiscovery returns a new LightsailDiscovery which periodically refreshes its targets.
@@ -157,46 +165,44 @@ func NewLightsailDiscovery(conf *LightsailSDConfig, logger *slog.Logger, metrics
return d, nil
}
func (d *LightsailDiscovery) lightsailClient() (*lightsail.Lightsail, error) {
func (d *LightsailDiscovery) lightsailClient(ctx context.Context) (*lightsail.Client, error) {
if d.lightsail != nil {
return d.lightsail, nil
}
creds := credentials.NewStaticCredentials(d.cfg.AccessKey, string(d.cfg.SecretKey), "")
if d.cfg.AccessKey == "" && d.cfg.SecretKey == "" {
creds = nil
}
credProvider := credentials.NewStaticCredentialsProvider(d.cfg.AccessKey, string(d.cfg.SecretKey), "")
client, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "lightsail_sd")
// Build the HTTP client from the provided HTTPClientConfig.
httpClient, err := config.NewClientFromConfig(d.cfg.HTTPClientConfig, "lightsail_sd")
if err != nil {
return nil, err
}
sess, err := session.NewSessionWithOptions(session.Options{
Config: aws.Config{
Endpoint: &d.cfg.Endpoint,
Region: &d.cfg.Region,
Credentials: creds,
HTTPClient: client,
},
Profile: d.cfg.Profile,
})
// Build the AWS config with the provided region and credentials.
cfg, err := awsConfig.LoadDefaultConfig(
ctx,
awsConfig.WithRegion(d.cfg.Region),
awsConfig.WithCredentialsProvider(credProvider),
awsConfig.WithSharedConfigProfile(d.cfg.Profile),
awsConfig.WithHTTPClient(httpClient),
)
if err != nil {
return nil, fmt.Errorf("could not create aws session: %w", err)
return nil, fmt.Errorf("could not create aws config: %w", err)
}
// If the role ARN is set, assume the role to get credentials and set the credentials provider in the config.
if d.cfg.RoleARN != "" {
creds := stscreds.NewCredentials(sess, d.cfg.RoleARN)
d.lightsail = lightsail.New(sess, &aws.Config{Credentials: creds})
} else {
d.lightsail = lightsail.New(sess)
assumeProvider := stscreds.NewAssumeRoleProvider(sts.NewFromConfig(cfg), d.cfg.RoleARN)
cfg.Credentials = aws.NewCredentialsCache(assumeProvider)
}
d.lightsail = lightsail.NewFromConfig(cfg)
return d.lightsail, nil
}
func (d *LightsailDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
lightsailClient, err := d.lightsailClient()
lightsailClient, err := d.lightsailClient(ctx)
if err != nil {
return nil, err
}
@@ -207,10 +213,10 @@ func (d *LightsailDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
input := &lightsail.GetInstancesInput{}
output, err := lightsailClient.GetInstancesWithContext(ctx, input)
output, err := lightsailClient.GetInstances(ctx, input)
if err != nil {
var awsErr awserr.Error
if errors.As(err, &awsErr) && (awsErr.Code() == "AuthFailure" || awsErr.Code() == "UnauthorizedOperation") {
var awsErr smithy.APIError
if errors.As(err, &awsErr) && (awsErr.ErrorCode() == "AuthFailure" || awsErr.ErrorCode() == "UnauthorizedOperation") {
d.lightsail = nil
}
return nil, fmt.Errorf("could not get instances: %w", err)
@@ -241,9 +247,7 @@ func (d *LightsailDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
if len(inst.Ipv6Addresses) > 0 {
var ipv6addrs []string
for _, ipv6addr := range inst.Ipv6Addresses {
ipv6addrs = append(ipv6addrs, *ipv6addr)
}
ipv6addrs = append(ipv6addrs, inst.Ipv6Addresses...)
labels[lightsailLabelIPv6Addresses] = model.LabelValue(
lightsailLabelSeparator +
strings.Join(ipv6addrs, lightsailLabelSeparator) +
@@ -251,7 +255,7 @@ func (d *LightsailDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group,
}
for _, t := range inst.Tags {
if t == nil || t.Key == nil || t.Value == nil {
if t.Key == nil || t.Value == nil {
continue
}
name := strutil.SanitizeLabelName(*t.Key)

24
go.mod
View File

@@ -11,7 +11,14 @@ require (
github.com/KimMachineGun/automemlimit v0.7.3
github.com/alecthomas/kingpin/v2 v2.4.0
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b
github.com/aws/aws-sdk-go v1.55.7
github.com/aws/aws-sdk-go-v2 v1.37.0
github.com/aws/aws-sdk-go-v2/config v1.29.14
github.com/aws/aws-sdk-go-v2/credentials v1.17.67
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30
github.com/aws/aws-sdk-go-v2/service/ec2 v1.237.0
github.com/aws/aws-sdk-go-v2/service/lightsail v1.44.0
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19
github.com/aws/smithy-go v1.22.5
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3
github.com/cespare/xxhash/v2 v2.3.0
github.com/dennwc/varint v1.0.0
@@ -95,19 +102,13 @@ require (
)
require (
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
github.com/aws/smithy-go v1.22.2 // indirect
github.com/cenkalti/backoff/v5 v5.0.2 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
@@ -174,7 +175,6 @@ require (
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v0.6.0 // indirect
github.com/hashicorp/serf v0.10.1 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect

35
go.sum
View File

@@ -49,34 +49,36 @@ github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE=
github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.36.3 h1:mJoei2CxPutQVxaATCzDUjcZEjVRdpsiiXi2o38yqWM=
github.com/aws/aws-sdk-go-v2 v1.36.3/go.mod h1:LLXuLpgzEbD766Z5ECcRmi8AzSwfZItDtmABVkRLGzg=
github.com/aws/aws-sdk-go-v2 v1.37.0 h1:YtCOESR/pN4j5oA7cVHSfOwIcuh/KwHC4DOSXFbv5F0=
github.com/aws/aws-sdk-go-v2 v1.37.0/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg=
github.com/aws/aws-sdk-go-v2/config v1.29.14 h1:f+eEi/2cKCg9pqKBoAIwRGzVb70MRKqWX4dg1BDcSJM=
github.com/aws/aws-sdk-go-v2/config v1.29.14/go.mod h1:wVPHWcIFv3WO89w0rE10gzf17ZYy+UVS1Geq8Iei34g=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 h1:9KxtdcIA/5xPNQyZRgUSpYOE6j9Bc4+D7nZua0KGYOM=
github.com/aws/aws-sdk-go-v2/credentials v1.17.67/go.mod h1:p3C44m+cfnbv763s52gCqrjaqyPikj9Sg47kUVaNZQQ=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 h1:x793wxmUWVDhshP8WW2mlnXuFrO4cOd3HLBroh1paFw=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30/go.mod h1:Jpne2tDnYiFascUEs2AWHJL9Yp7A5ZVy3TNyxaAjD6M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 h1:ZK5jHhnrioRkUNOc+hOgQKlUL5JeC3S6JgLxtQ+Rm0Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34/go.mod h1:p4VfIceZokChbA9FzMbRGz5OV+lekcVtHlPKEO0gSZY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 h1:SZwFm17ZUNNg5Np0ioo/gq8Mn6u9w19Mri8DnJ15Jf0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34/go.mod h1:dFZsC0BLo346mvKQLWmoJxT+Sjp+qcVR1tRVHQGOH9Q=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0 h1:H2iZoqW/v2Jnrh1FnU725Bq6KJ0k2uP63yH+DcY+HUI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.0/go.mod h1:L0FqLbwMXHvNC/7crWV1iIxUlOKYZUE8KuTIA+TozAI=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0 h1:EDped/rNzAhFPhVY0sDGbtD16OKqksfA8OjF/kLEgw8=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.0/go.mod h1:uUI335jvzpZRPpjYx6ODc/wg1qH+NnoSTK/FwVeK0C0=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 h1:eAh2A4b5IzM/lum78bZ590jy36+d/aFLgKF/4Vd1xPE=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3/go.mod h1:0yKJC/kb8sAnmlYa6Zs3QVYqaC8ug2AbnNChv5Ox3uA=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 h1:dM9/92u2F1JbDaGooxTq18wmmFzbJRfXfVfy96/1CXM=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15/go.mod h1:SwFBy2vjtA0vZbjjaFtfN045boopadnoVPhu4Fv66vY=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.237.0 h1:XHE2G+yaDQql32FZt19QmQt4WuisqQJIkMUSCxeCUl8=
github.com/aws/aws-sdk-go-v2/service/ec2 v1.237.0/go.mod h1:t11/j/nH9i6bbsPH9xc04BJOsV2nVPUqrB67/TLDsyM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0 h1:eRhU3Sh8dGbaniI6B+I48XJMrTPRkK4DKo+vqIxziOU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.0/go.mod h1:paNLV18DZ6FnWE/bd06RIKPDIFpjuvCkGKWTG/GDBeM=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.44.0 h1:QiiCqpKy0prxq+92uWfESzcb7/8Y9JAamcMOzVYLEoM=
github.com/aws/aws-sdk-go-v2/service/lightsail v1.44.0/go.mod h1:ESppxYqXQCpCY+KWl3BdkQjmsQX6zxKP39SnDtRDoU0=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 h1:1Gw+9ajCV1jogloEv1RRnvfRFia2cL6c9cuKV2Ps+G8=
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3/go.mod h1:qs4a9T5EMLl/Cajiw2TcbNt2UNo/Hqlyp+GiuG4CFDI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 h1:hXmVKytPfTy5axZ+fYbR5d0cFmC3JvwLm5kM83luako=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1/go.mod h1:MlYRNmYu/fGPoxBQVvBYr9nyr948aY/WLUvwBMBJubs=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 h1:1XuUZ8mYJw9B6lzAkXhqHlJd/XvaX32evhproijJEZY=
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19/go.mod h1:cQnB8CUnxbMU82JvlqjKR2HBOm3fe9pWorWBza6MBJ4=
github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ=
github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw=
github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 h1:6df1vn4bBlDDo4tARvBm7l6KA9iVMnE3NWizDeWSrps=
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3/go.mod h1:CIWtjkly68+yqLPbvwwR/fjNJA/idrtULjZWh2v1ys0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@@ -289,10 +291,6 @@ github.com/ionos-cloud/sdk-go/v6 v6.3.4 h1:jTvGl4LOF8v8OYoEIBNVwbFoqSGAFqn6vGE7s
github.com/ionos-cloud/sdk-go/v6 v6.3.4/go.mod h1:wCVwNJ/21W29FWFUv+fNawOTMlFoP1dS3L+ZuztFW48=
github.com/jarcoal/httpmock v1.4.0 h1:BvhqnH0JAYbNudL2GMJKgOHe2CtKlzJ/5rWKyp+hc2k=
github.com/jarcoal/httpmock v1.4.0/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
@@ -708,7 +706,6 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=