mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2024-12-05 01:01:09 +01:00
312fead9a2
* Add improvements to ec2 discovery https://github.com/VictoriaMetrics/VictoriaMetrics/issues/771 role_arn support with aws sts instance iam_role support refreshing temporary tokens * Apply suggestions from code review Co-authored-by: Roman Khavronenko <hagen1778@gmail.com> * changed implementation, removed tests, clean up code * moved endpoint builder into getEC2APIResponse Co-authored-by: Roman Khavronenko <hagen1778@gmail.com>
180 lines
6.5 KiB
Go
180 lines
6.5 KiB
Go
package ec2
|
|
|
|
import (
|
|
"encoding/xml"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promscrape/discoveryutils"
|
|
)
|
|
|
|
// getInstancesLabels returns labels for ec2 instances obtained from the given cfg
|
|
func getInstancesLabels(cfg *apiConfig) ([]map[string]string, error) {
|
|
rs, err := getReservations(cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var ms []map[string]string
|
|
for _, r := range rs {
|
|
for _, inst := range r.InstanceSet.Items {
|
|
ms = inst.appendTargetLabels(ms, r.OwnerID, cfg.port)
|
|
}
|
|
}
|
|
return ms, nil
|
|
}
|
|
|
|
func getReservations(cfg *apiConfig) ([]Reservation, error) {
|
|
// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html
|
|
action := "DescribeInstances"
|
|
var rs []Reservation
|
|
pageToken := ""
|
|
for {
|
|
data, err := getEC2APIResponse(cfg, action, pageToken)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot obtain instances: %w", err)
|
|
}
|
|
ir, err := parseInstancesResponse(data)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot parse instance list: %w", err)
|
|
}
|
|
rs = append(rs, ir.ReservationSet.Items...)
|
|
if len(ir.NextPageToken) == 0 {
|
|
return rs, nil
|
|
}
|
|
pageToken = ir.NextPageToken
|
|
}
|
|
}
|
|
|
|
// InstancesResponse represents response to https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html
|
|
type InstancesResponse struct {
|
|
ReservationSet ReservationSet `xml:"reservationSet"`
|
|
NextPageToken string `xml:"nextToken"`
|
|
}
|
|
|
|
// ReservationSet represetns ReservationSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html
|
|
type ReservationSet struct {
|
|
Items []Reservation `xml:"item"`
|
|
}
|
|
|
|
// Reservation represents Reservation from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Reservation.html
|
|
type Reservation struct {
|
|
OwnerID string `xml:"ownerId"`
|
|
InstanceSet InstanceSet `xml:"instancesSet"`
|
|
}
|
|
|
|
// InstanceSet represents InstanceSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Reservation.html
|
|
type InstanceSet struct {
|
|
Items []Instance `xml:"item"`
|
|
}
|
|
|
|
// Instance represents Instance from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Instance.html
|
|
type Instance struct {
|
|
PrivateIPAddress string `xml:"privateIpAddress"`
|
|
Architecture string `xml:"architecture"`
|
|
Placement Placement `xml:"placement"`
|
|
ImageID string `xml:"imageId"`
|
|
ID string `xml:"instanceId"`
|
|
Lifecycle string `xml:"instanceLifecycle"`
|
|
State InstanceState `xml:"instanceState"`
|
|
Type string `xml:"instanceType"`
|
|
Platform string `xml:"platform"`
|
|
SubnetID string `xml:"subnetId"`
|
|
PrivateDNSName string `xml:"privateDnsName"`
|
|
PublicDNSName string `xml:"dnsName"`
|
|
PublicIPAddress string `xml:"ipAddress"`
|
|
VPCID string `xml:"vpcId"`
|
|
NetworkInterfaceSet NetworkInterfaceSet `xml:"networkInterfaceSet"`
|
|
TagSet TagSet `xml:"tagSet"`
|
|
}
|
|
|
|
// Placement represents Placement from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Placement.html
|
|
type Placement struct {
|
|
AvailabilityZone string `xml:"availabilityZone"`
|
|
}
|
|
|
|
// InstanceState represents InstanceState from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceState.html
|
|
type InstanceState struct {
|
|
Name string `xml:"name"`
|
|
}
|
|
|
|
// NetworkInterfaceSet represents NetworkInterfaceSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Instance.html
|
|
type NetworkInterfaceSet struct {
|
|
Items []NetworkInterface `xml:"item"`
|
|
}
|
|
|
|
// NetworkInterface represents NetworkInterface from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceNetworkInterface.html
|
|
type NetworkInterface struct {
|
|
SubnetID string `xml:"subnetId"`
|
|
}
|
|
|
|
// TagSet represents TagSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Instance.html
|
|
type TagSet struct {
|
|
Items []Tag `xml:"item"`
|
|
}
|
|
|
|
// Tag represents Tag from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_Tag.html
|
|
type Tag struct {
|
|
Key string `xml:"key"`
|
|
Value string `xml:"value"`
|
|
}
|
|
|
|
func parseInstancesResponse(data []byte) (*InstancesResponse, error) {
|
|
var v InstancesResponse
|
|
if err := xml.Unmarshal(data, &v); err != nil {
|
|
return nil, fmt.Errorf("cannot unmarshal InstancesResponse from %q: %w", data, err)
|
|
}
|
|
return &v, nil
|
|
}
|
|
|
|
func (inst *Instance) appendTargetLabels(ms []map[string]string, ownerID string, port int) []map[string]string {
|
|
if len(inst.PrivateIPAddress) == 0 {
|
|
// Cannot scrape instance without private IP address
|
|
return ms
|
|
}
|
|
addr := discoveryutils.JoinHostPort(inst.PrivateIPAddress, port)
|
|
m := map[string]string{
|
|
"__address__": addr,
|
|
"__meta_ec2_architecture": inst.Architecture,
|
|
"__meta_ec2_ami": inst.ImageID,
|
|
"__meta_ec2_availability_zone": inst.Placement.AvailabilityZone,
|
|
"__meta_ec2_instance_id": inst.ID,
|
|
"__meta_ec2_instance_lifecycle": inst.Lifecycle,
|
|
"__meta_ec2_instance_state": inst.State.Name,
|
|
"__meta_ec2_instance_type": inst.Type,
|
|
"__meta_ec2_owner_id": ownerID,
|
|
"__meta_ec2_platform": inst.Platform,
|
|
"__meta_ec2_primary_subnet_id": inst.SubnetID,
|
|
"__meta_ec2_private_dns_name": inst.PrivateDNSName,
|
|
"__meta_ec2_private_ip": inst.PrivateIPAddress,
|
|
"__meta_ec2_public_dns_name": inst.PublicDNSName,
|
|
"__meta_ec2_public_ip": inst.PublicIPAddress,
|
|
"__meta_ec2_vpc_id": inst.VPCID,
|
|
}
|
|
if len(inst.VPCID) > 0 {
|
|
// Deduplicate VPC Subnet IDs maintaining the order of the network interfaces returned by EC2.
|
|
subnets := make([]string, 0, len(inst.NetworkInterfaceSet.Items))
|
|
seenSubnets := make(map[string]bool, len(inst.NetworkInterfaceSet.Items))
|
|
for _, ni := range inst.NetworkInterfaceSet.Items {
|
|
if len(ni.SubnetID) == 0 {
|
|
continue
|
|
}
|
|
if !seenSubnets[ni.SubnetID] {
|
|
seenSubnets[ni.SubnetID] = true
|
|
subnets = append(subnets, ni.SubnetID)
|
|
}
|
|
}
|
|
// We surround the separated list with the separator as well. This way regular expressions
|
|
// in relabeling rules don't have to consider tag positions.
|
|
m["__meta_ec2_subnet_id"] = "," + strings.Join(subnets, ",") + ","
|
|
}
|
|
for _, t := range inst.TagSet.Items {
|
|
if len(t.Key) == 0 || len(t.Value) == 0 {
|
|
continue
|
|
}
|
|
name := discoveryutils.SanitizeLabelName(t.Key)
|
|
m["__meta_ec2_tag_"+name] = t.Value
|
|
}
|
|
ms = append(ms, m)
|
|
return ms
|
|
}
|