prometheus/discovery/scaleway/instance.go
Nandhis 80b1044a9e
Some checks are pending
buf.build / lint and publish (push) Waiting to run
CI / Go tests (push) Waiting to run
CI / More Go tests (push) Waiting to run
CI / Go tests for 32-bit x86 (push) Waiting to run
CI / Go tests for Prometheus upgrades and downgrades (push) Waiting to run
CI / Go tests with previous Go version (push) Waiting to run
CI / UI tests (push) Waiting to run
CI / Go tests on Windows (push) Waiting to run
CI / Mixins tests (push) Waiting to run
CI / Compliance testing (push) Waiting to run
CI / Build Prometheus for common architectures (push) Waiting to run
CI / Build Prometheus for all architectures (push) Waiting to run
CI / Report status of build Prometheus for all architectures (push) Blocked by required conditions
CI / Check generated parser (push) Waiting to run
CI / golangci-lint (push) Waiting to run
CI / fuzzing (push) Waiting to run
CI / codeql (push) Waiting to run
CI / Publish main branch artifacts (push) Blocked by required conditions
CI / Publish release artefacts (push) Blocked by required conditions
CI / Publish UI on npm Registry (push) Blocked by required conditions
govulncheck / Run govulncheck (push) Waiting to run
Scorecards supply-chain security / Scorecards analysis (push) Waiting to run
discovery/scaleway: discover IPAM private NIC addresses (#18772)
Signed-off-by: msnandhis <45960035+msnandhis@users.noreply.github.com>
2026-05-26 16:17:42 +02:00

279 lines
9.2 KiB
Go

// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package scaleway
import (
"context"
"fmt"
"net"
"strconv"
"strings"
"github.com/prometheus/common/model"
"github.com/prometheus/common/version"
"github.com/scaleway/scaleway-sdk-go/api/instance/v1"
ipam "github.com/scaleway/scaleway-sdk-go/api/ipam/v1"
"github.com/scaleway/scaleway-sdk-go/scw"
"github.com/prometheus/prometheus/discovery/refresh"
"github.com/prometheus/prometheus/discovery/targetgroup"
)
const (
instanceLabelPrefix = metaLabelPrefix + "instance_"
instanceBootTypeLabel = instanceLabelPrefix + "boot_type"
instanceHostnameLabel = instanceLabelPrefix + "hostname"
instanceIDLabel = instanceLabelPrefix + "id"
instanceImageArchLabel = instanceLabelPrefix + "image_arch"
instanceImageIDLabel = instanceLabelPrefix + "image_id"
instanceImageNameLabel = instanceLabelPrefix + "image_name"
instanceLocationClusterID = instanceLabelPrefix + "location_cluster_id"
instanceLocationHypervisorID = instanceLabelPrefix + "location_hypervisor_id"
instanceLocationNodeID = instanceLabelPrefix + "location_node_id"
instanceNameLabel = instanceLabelPrefix + "name"
instanceOrganizationLabel = instanceLabelPrefix + "organization_id"
instancePrivateIPv4Label = instanceLabelPrefix + "private_ipv4"
instanceProjectLabel = instanceLabelPrefix + "project_id"
instancePublicIPv4Label = instanceLabelPrefix + "public_ipv4"
instancePublicIPv6Label = instanceLabelPrefix + "public_ipv6"
instancePublicIPv4AddressesLabel = instanceLabelPrefix + "public_ipv4_addresses"
instancePublicIPv6AddressesLabel = instanceLabelPrefix + "public_ipv6_addresses"
instanceSecurityGroupIDLabel = instanceLabelPrefix + "security_group_id"
instanceSecurityGroupNameLabel = instanceLabelPrefix + "security_group_name"
instanceStateLabel = instanceLabelPrefix + "status"
instanceTagsLabel = instanceLabelPrefix + "tags"
instanceTypeLabel = instanceLabelPrefix + "type"
instanceZoneLabel = instanceLabelPrefix + "zone"
instanceRegionLabel = instanceLabelPrefix + "region"
)
type instanceDiscovery struct {
*refresh.Discovery
client *scw.Client
port int
zone string
project string
accessKey string
secretKey string
nameFilter string
tagsFilter []string
}
func newInstanceDiscovery(conf *SDConfig) (*instanceDiscovery, error) {
d := &instanceDiscovery{
port: conf.Port,
zone: conf.Zone,
project: conf.Project,
accessKey: conf.AccessKey,
secretKey: conf.secretKeyForConfig(),
nameFilter: conf.NameFilter,
tagsFilter: conf.TagsFilter,
}
client, err := newScalewayHTTPClient(conf)
if err != nil {
return nil, err
}
profile, err := loadProfile(conf)
if err != nil {
return nil, err
}
d.client, err = scw.NewClient(
scw.WithHTTPClient(client),
scw.WithUserAgent(version.PrometheusUserAgent()),
scw.WithProfile(profile),
)
if err != nil {
return nil, fmt.Errorf("error setting up scaleway client: %w", err)
}
return d, nil
}
func (d *instanceDiscovery) refresh(ctx context.Context) ([]*targetgroup.Group, error) {
instanceAPI := instance.NewAPI(d.client)
ipamAPI := ipam.NewAPI(d.client)
req := &instance.ListServersRequest{}
if d.nameFilter != "" {
req.Name = scw.StringPtr(d.nameFilter)
}
if d.tagsFilter != nil {
req.Tags = d.tagsFilter
}
servers, err := instanceAPI.ListServers(req, scw.WithAllPages(), scw.WithContext(ctx))
if err != nil {
return nil, err
}
privateNICIPByID, err := privateNICIPs(ctx, ipamAPI, servers.Servers)
if err != nil {
return nil, err
}
var targets []model.LabelSet
for _, server := range servers.Servers {
labels := model.LabelSet{
instanceBootTypeLabel: model.LabelValue(server.BootType),
instanceHostnameLabel: model.LabelValue(server.Hostname),
instanceIDLabel: model.LabelValue(server.ID),
instanceNameLabel: model.LabelValue(server.Name),
instanceOrganizationLabel: model.LabelValue(server.Organization),
instanceProjectLabel: model.LabelValue(server.Project),
instanceStateLabel: model.LabelValue(server.State),
instanceTypeLabel: model.LabelValue(server.CommercialType),
instanceZoneLabel: model.LabelValue(server.Zone.String()),
}
if server.Image != nil {
labels[instanceImageArchLabel] = model.LabelValue(server.Image.Arch)
labels[instanceImageIDLabel] = model.LabelValue(server.Image.ID)
labels[instanceImageNameLabel] = model.LabelValue(server.Image.Name)
}
if server.Location != nil {
labels[instanceLocationClusterID] = model.LabelValue(server.Location.ClusterID)
labels[instanceLocationHypervisorID] = model.LabelValue(server.Location.HypervisorID)
labels[instanceLocationNodeID] = model.LabelValue(server.Location.NodeID)
}
if server.SecurityGroup != nil {
labels[instanceSecurityGroupIDLabel] = model.LabelValue(server.SecurityGroup.ID)
labels[instanceSecurityGroupNameLabel] = model.LabelValue(server.SecurityGroup.Name)
}
if region, err := server.Zone.Region(); err == nil {
labels[instanceRegionLabel] = model.LabelValue(region.String())
}
if len(server.Tags) > 0 {
// We surround the separated list with the separator as well. This way regular expressions
// in relabeling rules don't have to consider tag positions.
tags := separator + strings.Join(server.Tags, separator) + separator
labels[instanceTagsLabel] = model.LabelValue(tags)
}
addr := ""
if len(server.PublicIPs) > 0 {
var ipv4Addresses []string
var ipv6Addresses []string
for _, ip := range server.PublicIPs {
switch ip.Family {
case instance.ServerIPIPFamilyInet:
ipv4Addresses = append(ipv4Addresses, ip.Address.String())
case instance.ServerIPIPFamilyInet6:
ipv6Addresses = append(ipv6Addresses, ip.Address.String())
}
}
if len(ipv6Addresses) > 0 {
labels[instancePublicIPv6AddressesLabel] = model.LabelValue(
separator +
strings.Join(ipv6Addresses, separator) +
separator)
}
if len(ipv4Addresses) > 0 {
labels[instancePublicIPv4AddressesLabel] = model.LabelValue(
separator +
strings.Join(ipv4Addresses, separator) +
separator)
}
}
if server.IPv6 != nil { //nolint:staticcheck
labels[instancePublicIPv6Label] = model.LabelValue(server.IPv6.Address.String()) //nolint:staticcheck
addr = server.IPv6.Address.String() //nolint:staticcheck
}
if server.PublicIP != nil { //nolint:staticcheck
if server.PublicIP.Family != instance.ServerIPIPFamilyInet6 { //nolint:staticcheck
labels[instancePublicIPv4Label] = model.LabelValue(server.PublicIP.Address.String()) //nolint:staticcheck
}
addr = server.PublicIP.Address.String() //nolint:staticcheck
}
if server.PrivateIP != nil {
labels[instancePrivateIPv4Label] = model.LabelValue(*server.PrivateIP)
addr = *server.PrivateIP
}
if addr == "" {
for _, privateNIC := range server.PrivateNics {
if privateNIC == nil {
continue
}
if privateIP := privateNICIPByID[privateNIC.ID]; privateIP != "" {
labels[instancePrivateIPv4Label] = model.LabelValue(privateIP)
addr = privateIP
break
}
}
}
if addr != "" {
addr := net.JoinHostPort(addr, strconv.FormatUint(uint64(d.port), 10))
labels[model.AddressLabel] = model.LabelValue(addr)
targets = append(targets, labels)
}
}
return []*targetgroup.Group{{Source: "scaleway", Targets: targets}}, nil
}
func privateNICIPs(ctx context.Context, api *ipam.API, servers []*instance.Server) (map[string]string, error) {
privateNICIDsByRegion := map[scw.Region][]string{}
for _, server := range servers {
if server.PrivateIP != nil || server.PublicIP != nil || server.IPv6 != nil { //nolint:staticcheck
continue
}
region, err := server.Zone.Region()
if err != nil {
return nil, err
}
for _, privateNIC := range server.PrivateNics {
if privateNIC != nil && privateNIC.ID != "" {
privateNICIDsByRegion[region] = append(privateNICIDsByRegion[region], privateNIC.ID)
}
}
}
privateNICIPs := map[string]string{}
for region, privateNICIDs := range privateNICIDsByRegion {
ips, err := api.ListIPs(&ipam.ListIPsRequest{
Region: region,
ResourceIDs: privateNICIDs,
ResourceTypes: []ipam.ResourceType{ipam.ResourceTypeInstancePrivateNic},
}, scw.WithAllPages(), scw.WithContext(ctx))
if err != nil {
return nil, err
}
for _, ip := range ips.IPs {
if ip != nil && ip.Resource != nil && !ip.IsIPv6 && ip.Address.IP != nil {
privateNICIPs[ip.Resource.ID] = ip.Address.IP.String()
}
}
}
return privateNICIPs, nil
}