2022-08-04 17:44:16 +00:00
|
|
|
package yandexcloud
|
|
|
|
|
|
|
|
import (
|
2022-08-05 07:30:47 +00:00
|
|
|
"encoding/json"
|
2022-08-04 17:44:16 +00:00
|
|
|
"flag"
|
|
|
|
"fmt"
|
2022-08-04 19:26:38 +00:00
|
|
|
"net/url"
|
2022-08-04 17:44:16 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promauth"
|
2022-11-30 05:22:12 +00:00
|
|
|
"github.com/VictoriaMetrics/VictoriaMetrics/lib/promutils"
|
2022-08-04 17:44:16 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// SDCheckInterval defines interval for targets refresh.
|
|
|
|
var SDCheckInterval = flag.Duration("promscrape.yandexcloudSDCheckInterval", 30*time.Second, "Interval for checking for changes in Yandex Cloud API. "+
|
2022-08-14 22:40:20 +00:00
|
|
|
"This works only if yandexcloud_sd_configs is configured in '-promscrape.config' file. "+
|
|
|
|
"See https://docs.victoriametrics.com/sd_configs.html#yandexcloud_sd_configs for details")
|
2022-08-04 17:44:16 +00:00
|
|
|
|
|
|
|
// SDConfig is the configuration for Yandex Cloud service discovery.
|
|
|
|
type SDConfig struct {
|
|
|
|
Service string `yaml:"service"`
|
|
|
|
YandexPassportOAuthToken *promauth.Secret `yaml:"yandex_passport_oauth_token,omitempty"`
|
|
|
|
APIEndpoint string `yaml:"api_endpoint,omitempty"`
|
|
|
|
TLSConfig *promauth.TLSConfig `yaml:"tls_config,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLabels returns labels for Yandex Cloud according to service discover config.
|
2022-11-30 05:22:12 +00:00
|
|
|
func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
|
2022-08-04 17:44:16 +00:00
|
|
|
cfg, err := getAPIConfig(sdc, baseDir)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot get API config: %w", err)
|
|
|
|
}
|
|
|
|
switch sdc.Service {
|
|
|
|
case "compute":
|
|
|
|
return getInstancesLabels(cfg)
|
|
|
|
default:
|
2023-10-25 21:19:33 +00:00
|
|
|
return nil, fmt.Errorf("skipping unexpected service=%q; only `compute` supported for now", sdc.Service)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cfg *apiConfig) getInstances(folderID string) ([]instance, error) {
|
2022-08-04 19:26:38 +00:00
|
|
|
instancesURL := cfg.serviceEndpoints["compute"] + "/compute/v1/instances"
|
|
|
|
instancesURL += "?folderId=" + url.QueryEscape(folderID)
|
2022-08-04 17:44:16 +00:00
|
|
|
|
2022-08-04 19:26:38 +00:00
|
|
|
var instances []instance
|
|
|
|
nextLink := instancesURL
|
2022-08-04 17:44:16 +00:00
|
|
|
for {
|
2022-08-04 19:26:38 +00:00
|
|
|
data, err := getAPIResponse(nextLink, cfg)
|
2022-08-04 17:44:16 +00:00
|
|
|
if err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot get instances: %w", err)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-05 07:30:47 +00:00
|
|
|
var ip instancesPage
|
|
|
|
if err := json.Unmarshal(data, &ip); err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse instances response from %q: %w; response body: %s", nextLink, err, data)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
instances = append(instances, ip.Instances...)
|
|
|
|
if len(ip.NextPageToken) == 0 {
|
2022-08-04 17:44:16 +00:00
|
|
|
return instances, nil
|
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
nextLink = instancesURL + "&pageToken=" + url.QueryEscape(ip.NextPageToken)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:30:47 +00:00
|
|
|
// See https://cloud.yandex.com/en-ru/docs/compute/api-ref/Instance/list
|
|
|
|
type instancesPage struct {
|
|
|
|
Instances []instance `json:"instances"`
|
|
|
|
NextPageToken string `json:"nextPageToken"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type instance struct {
|
|
|
|
ID string `json:"id"`
|
|
|
|
Name string `json:"name"`
|
|
|
|
FQDN string `json:"fqdn"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
FolderID string `json:"folderId"`
|
|
|
|
PlatformID string `json:"platformId"`
|
|
|
|
Resources resources `json:"resources"`
|
|
|
|
NetworkInterfaces []networkInterface `json:"networkInterfaces"`
|
|
|
|
Labels map[string]string `json:"labels,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type resources struct {
|
|
|
|
Cores string `json:"cores"`
|
|
|
|
CoreFraction string `json:"coreFraction"`
|
|
|
|
Memory string `json:"memory"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type networkInterface struct {
|
|
|
|
Index string `json:"index"`
|
|
|
|
MacAddress string `json:"macAddress"`
|
|
|
|
SubnetID string `json:"subnetId"`
|
|
|
|
PrimaryV4Address primaryV4Address `json:"primaryV4Address"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type primaryV4Address struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
OneToOneNat oneToOneNat `json:"oneToOneNat"`
|
|
|
|
DNSRecords []dnsRecord `json:"dnsRecords"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type oneToOneNat struct {
|
|
|
|
Address string `json:"address"`
|
|
|
|
IPVersion string `json:"ipVersion"`
|
|
|
|
DNSRecords []dnsRecord `json:"dnsRecords"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type dnsRecord struct {
|
|
|
|
FQDN string `json:"fqdn"`
|
|
|
|
DNSZoneID string `json:"dnsZoneId"`
|
|
|
|
TTL string `json:"ttl"`
|
|
|
|
PTR bool `json:"ptr"`
|
|
|
|
}
|
|
|
|
|
2022-08-04 17:44:16 +00:00
|
|
|
func (cfg *apiConfig) getFolders(clouds []cloud) ([]folder, error) {
|
2022-08-04 19:26:38 +00:00
|
|
|
foldersURL := cfg.serviceEndpoints["resource-manager"] + "/resource-manager/v1/folders"
|
|
|
|
var folders []folder
|
2022-08-04 17:44:16 +00:00
|
|
|
for _, cl := range clouds {
|
2022-08-04 19:26:38 +00:00
|
|
|
cloudURL := foldersURL + "?cloudId=" + url.QueryEscape(cl.ID)
|
|
|
|
nextLink := cloudURL
|
2022-08-04 17:44:16 +00:00
|
|
|
for {
|
2022-08-04 19:26:38 +00:00
|
|
|
data, err := getAPIResponse(nextLink, cfg)
|
2022-08-04 17:44:16 +00:00
|
|
|
if err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot get folders: %w", err)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-05 07:30:47 +00:00
|
|
|
var fp foldersPage
|
|
|
|
if err := json.Unmarshal(data, &fp); err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse folders response from %q: %w; response body: %s", nextLink, err, data)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
folders = append(folders, fp.Folders...)
|
|
|
|
if len(fp.NextPageToken) == 0 {
|
2022-08-04 17:44:16 +00:00
|
|
|
break
|
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
nextLink = cloudURL + "&pageToken=" + url.QueryEscape(fp.NextPageToken)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return folders, nil
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:30:47 +00:00
|
|
|
// See https://cloud.yandex.com/en-ru/docs/resource-manager/api-ref/Folder/list
|
|
|
|
type foldersPage struct {
|
|
|
|
Folders []folder `json:"folders"`
|
|
|
|
NextPageToken string `json:"nextPageToken"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type folder struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
ID string `json:"id"`
|
|
|
|
CloudID string `json:"cloudId"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
Status string `json:"status"`
|
|
|
|
Labels map[string]string `json:"labels"`
|
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
}
|
|
|
|
|
2022-08-04 19:26:38 +00:00
|
|
|
func (cfg *apiConfig) getClouds(orgs []organization) ([]cloud, error) {
|
|
|
|
cloudsURL := cfg.serviceEndpoints["resource-manager"] + "/resource-manager/v1/clouds"
|
|
|
|
if len(orgs) == 0 {
|
|
|
|
orgs = append(orgs, organization{
|
2022-08-04 17:44:16 +00:00
|
|
|
ID: "",
|
|
|
|
})
|
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
var clouds []cloud
|
|
|
|
for _, org := range orgs {
|
|
|
|
orgURL := cloudsURL
|
2022-08-04 17:44:16 +00:00
|
|
|
if org.ID != "" {
|
2022-08-04 19:26:38 +00:00
|
|
|
orgURL += "?organizationId=" + url.QueryEscape(org.ID)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
nextLink := orgURL
|
2022-08-04 17:44:16 +00:00
|
|
|
for {
|
2022-08-04 19:26:38 +00:00
|
|
|
data, err := getAPIResponse(nextLink, cfg)
|
2022-08-04 17:44:16 +00:00
|
|
|
if err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot get clouds: %w", err)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-05 07:30:47 +00:00
|
|
|
var cp cloudsPage
|
|
|
|
if err := json.Unmarshal(data, &cp); err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse clouds response from %q: %w; response body: %s", nextLink, err, data)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
clouds = append(clouds, cp.Clouds...)
|
|
|
|
if len(cp.NextPageToken) == 0 {
|
2022-08-04 17:44:16 +00:00
|
|
|
break
|
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
nextLink = orgURL + "&pageToken=" + url.QueryEscape(cp.NextPageToken)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return clouds, nil
|
|
|
|
}
|
|
|
|
|
2022-08-05 07:30:47 +00:00
|
|
|
// See https://cloud.yandex.com/en-ru/docs/resource-manager/api-ref/Cloud/list
|
|
|
|
type cloudsPage struct {
|
|
|
|
Clouds []cloud `json:"clouds"`
|
|
|
|
NextPageToken string `json:"nextPageToken"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type cloud struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
ID string `json:"id"`
|
|
|
|
Labels map[string]string `json:"labels"`
|
|
|
|
OrganizationID string `json:"organizationId"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
}
|
|
|
|
|
2022-08-04 17:44:16 +00:00
|
|
|
func (cfg *apiConfig) getOrganizations() ([]organization, error) {
|
2022-08-04 19:26:38 +00:00
|
|
|
orgsURL := cfg.serviceEndpoints["organization-manager"] + "/organization-manager/v1/organizations"
|
|
|
|
var orgs []organization
|
|
|
|
nextLink := orgsURL
|
2022-08-04 17:44:16 +00:00
|
|
|
for {
|
2022-08-04 19:26:38 +00:00
|
|
|
data, err := getAPIResponse(nextLink, cfg)
|
2022-08-04 17:44:16 +00:00
|
|
|
if err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot get organizations: %w", err)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-05 07:30:47 +00:00
|
|
|
var op organizationsPage
|
|
|
|
if err := json.Unmarshal(data, &op); err != nil {
|
2022-08-04 19:26:38 +00:00
|
|
|
return nil, fmt.Errorf("cannot parse organizations response from %q: %w; response body: %s", nextLink, err, data)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
orgs = append(orgs, op.Organizations...)
|
|
|
|
if len(op.NextPageToken) == 0 {
|
|
|
|
return orgs, nil
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
2022-08-04 19:26:38 +00:00
|
|
|
nextLink = orgsURL + "&pageToken=" + url.QueryEscape(op.NextPageToken)
|
2022-08-04 17:44:16 +00:00
|
|
|
}
|
|
|
|
}
|
2022-08-05 07:30:47 +00:00
|
|
|
|
|
|
|
// See https://cloud.yandex.com/en-ru/docs/organization/api-ref/Organization/list
|
|
|
|
type organizationsPage struct {
|
|
|
|
Organizations []organization `json:"organizations"`
|
|
|
|
NextPageToken string `json:"nextPageToken"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type organization struct {
|
|
|
|
Name string `json:"name"`
|
|
|
|
ID string `json:"id"`
|
|
|
|
Labels map[string]string `json:"labels"`
|
|
|
|
Title string `json:"title"`
|
|
|
|
Description string `json:"description"`
|
|
|
|
CreatedAt time.Time `json:"createdAt"`
|
|
|
|
}
|