-
Notifications
You must be signed in to change notification settings - Fork 34
/
yandexcloud.go
236 lines (213 loc) · 7.74 KB
/
yandexcloud.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
package yandexcloud
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/cprobe/cprobe/lib/promauth"
"github.com/cprobe/cprobe/lib/promutils"
)
// SDCheckInterval defines interval for targets refresh.
// var SDCheckInterval = flag.Duration("scrape.yandexcloudSDCheckInterval", 30*time.Second, "Interval for checking for changes in Yandex Cloud API. "+
// "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")
// 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.
func (sdc *SDConfig) GetLabels(baseDir string) ([]*promutils.Labels, error) {
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:
return nil, fmt.Errorf("skipping unexpected service=%q; only `compute` supported for now", sdc.Service)
}
}
func (cfg *apiConfig) getInstances(folderID string) ([]instance, error) {
instancesURL := cfg.serviceEndpoints["compute"] + "/compute/v1/instances"
instancesURL += "?folderId=" + url.QueryEscape(folderID)
var instances []instance
nextLink := instancesURL
for {
data, err := getAPIResponse(nextLink, cfg)
if err != nil {
return nil, fmt.Errorf("cannot get instances: %w", err)
}
var ip instancesPage
if err := json.Unmarshal(data, &ip); err != nil {
return nil, fmt.Errorf("cannot parse instances response from %q: %w; response body: %s", nextLink, err, data)
}
instances = append(instances, ip.Instances...)
if len(ip.NextPageToken) == 0 {
return instances, nil
}
nextLink = instancesURL + "&pageToken=" + url.QueryEscape(ip.NextPageToken)
}
}
// 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"`
}
func (cfg *apiConfig) getFolders(clouds []cloud) ([]folder, error) {
foldersURL := cfg.serviceEndpoints["resource-manager"] + "/resource-manager/v1/folders"
var folders []folder
for _, cl := range clouds {
cloudURL := foldersURL + "?cloudId=" + url.QueryEscape(cl.ID)
nextLink := cloudURL
for {
data, err := getAPIResponse(nextLink, cfg)
if err != nil {
return nil, fmt.Errorf("cannot get folders: %w", err)
}
var fp foldersPage
if err := json.Unmarshal(data, &fp); err != nil {
return nil, fmt.Errorf("cannot parse folders response from %q: %w; response body: %s", nextLink, err, data)
}
folders = append(folders, fp.Folders...)
if len(fp.NextPageToken) == 0 {
break
}
nextLink = cloudURL + "&pageToken=" + url.QueryEscape(fp.NextPageToken)
}
}
return folders, nil
}
// 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"`
}
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{
ID: "",
})
}
var clouds []cloud
for _, org := range orgs {
orgURL := cloudsURL
if org.ID != "" {
orgURL += "?organizationId=" + url.QueryEscape(org.ID)
}
nextLink := orgURL
for {
data, err := getAPIResponse(nextLink, cfg)
if err != nil {
return nil, fmt.Errorf("cannot get clouds: %w", err)
}
var cp cloudsPage
if err := json.Unmarshal(data, &cp); err != nil {
return nil, fmt.Errorf("cannot parse clouds response from %q: %w; response body: %s", nextLink, err, data)
}
clouds = append(clouds, cp.Clouds...)
if len(cp.NextPageToken) == 0 {
break
}
nextLink = orgURL + "&pageToken=" + url.QueryEscape(cp.NextPageToken)
}
}
return clouds, nil
}
// 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"`
}
func (cfg *apiConfig) getOrganizations() ([]organization, error) {
orgsURL := cfg.serviceEndpoints["organization-manager"] + "/organization-manager/v1/organizations"
var orgs []organization
nextLink := orgsURL
for {
data, err := getAPIResponse(nextLink, cfg)
if err != nil {
return nil, fmt.Errorf("cannot get organizations: %w", err)
}
var op organizationsPage
if err := json.Unmarshal(data, &op); err != nil {
return nil, fmt.Errorf("cannot parse organizations response from %q: %w; response body: %s", nextLink, err, data)
}
orgs = append(orgs, op.Organizations...)
if len(op.NextPageToken) == 0 {
return orgs, nil
}
nextLink = orgsURL + "&pageToken=" + url.QueryEscape(op.NextPageToken)
}
}
// 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"`
}