VictoriaMetrics/lib/promscrape/discovery/ec2/instance.go
Aliaksandr Valialkin 0d0561ca8c
lib/awsapi: remove whitelist arg from GetFiltersQueryString(), since it may break new filters in the future
Let users decide which filters to use. If users start using disallowed filters, then AWS will return an error.
2022-05-09 15:34:56 +03:00

194 lines
7.2 KiB
Go

package ec2
import (
"encoding/xml"
"fmt"
"strings"
"github.com/VictoriaMetrics/VictoriaMetrics/lib/awsapi"
"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
}
azMap := getAZMap(cfg)
var ms []map[string]string
for _, r := range rs {
for _, inst := range r.InstanceSet.Items {
ms = inst.appendTargetLabels(ms, r.OwnerID, cfg.port, azMap)
}
}
return ms, nil
}
func getReservations(cfg *apiConfig) ([]Reservation, error) {
// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html
var rs []Reservation
pageToken := ""
instanceFilters := awsapi.GetFiltersQueryString(cfg.instanceFilters)
for {
data, err := cfg.awsConfig.GetEC2APIResponse("DescribeInstances", instanceFilters, 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"`
IPv6AddressesSet Ipv6AddressesSet `xml:"ipv6AddressesSet"`
}
// Ipv6AddressesSet represents ipv6AddressesSet from https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_InstanceNetworkInterface.html
type Ipv6AddressesSet struct {
Items []string `xml:"item"`
}
// 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, azMap map[string]string) []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_availability_zone_id": azMap[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 {
subnets := make([]string, 0, len(inst.NetworkInterfaceSet.Items))
seenSubnets := make(map[string]bool, len(inst.NetworkInterfaceSet.Items))
var ipv6Addrs []string
for _, ni := range inst.NetworkInterfaceSet.Items {
if len(ni.SubnetID) == 0 {
continue
}
// Deduplicate VPC Subnet IDs maintaining the order of the network interfaces returned by EC2.
if !seenSubnets[ni.SubnetID] {
seenSubnets[ni.SubnetID] = true
subnets = append(subnets, ni.SubnetID)
}
// Collect ipv6 addresses
ipv6Addrs = append(ipv6Addrs, ni.IPv6AddressesSet.Items...)
}
// 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, ",") + ","
if len(ipv6Addrs) > 0 {
m["__meta_ec2_ipv6_addresses"] = "," + strings.Join(ipv6Addrs, ",") + ","
}
}
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
}