servicediscovery.go raw
1 package azuredns
2
3 import (
4 "bytes"
5 "context"
6 "fmt"
7
8 "github.com/Azure/azure-sdk-for-go/sdk/azcore"
9 "github.com/Azure/azure-sdk-for-go/sdk/azcore/arm"
10 "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
11 "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph"
12 "github.com/go-acme/lego/v4/providers/dns/internal/ptr"
13 )
14
15 type ServiceDiscoveryZone struct {
16 Name string
17 SubscriptionID string
18 ResourceGroup string
19 }
20
21 const (
22 ResourceGraphTypePublicDNSZone = "microsoft.network/dnszones"
23 ResourceGraphTypePrivateDNSZone = "microsoft.network/privatednszones"
24 )
25
26 const ResourceGraphQueryOptionsTop int32 = 1000
27
28 // discoverDNSZones finds all visible Azure DNS zones based on optional subscriptionID, resourceGroup and serviceDiscovery filter using Kusto query.
29 func discoverDNSZones(ctx context.Context, config *Config, credentials azcore.TokenCredential) (map[string]ServiceDiscoveryZone, error) {
30 options := &arm.ClientOptions{
31 ClientOptions: azcore.ClientOptions{
32 Cloud: config.Environment,
33 },
34 }
35
36 client, err := armresourcegraph.NewClient(credentials, options)
37 if err != nil {
38 return nil, err
39 }
40
41 // Set options
42 requestOptions := &armresourcegraph.QueryRequestOptions{
43 ResultFormat: to.Ptr(armresourcegraph.ResultFormatObjectArray),
44 Top: to.Ptr(ResourceGraphQueryOptionsTop),
45 Skip: to.Ptr[int32](0),
46 }
47
48 zones := map[string]ServiceDiscoveryZone{}
49
50 for {
51 // create the query request
52 request := armresourcegraph.QueryRequest{
53 Query: to.Ptr(createGraphQuery(config)),
54 Options: requestOptions,
55 }
56
57 result, err := client.Resources(ctx, request, nil)
58 if err != nil {
59 return zones, err
60 }
61
62 resultList, ok := result.Data.([]any)
63 if !ok {
64 // got invalid or empty data, skipping
65 break
66 }
67
68 for _, row := range resultList {
69 rowData, ok := row.(map[string]any)
70 if !ok {
71 continue
72 }
73
74 zoneName, ok := rowData["name"].(string)
75 if !ok {
76 continue
77 }
78
79 if _, exists := zones[zoneName]; exists {
80 return zones, fmt.Errorf(`found duplicate dns zone "%s"`, zoneName)
81 }
82
83 zones[zoneName] = ServiceDiscoveryZone{
84 Name: zoneName,
85 ResourceGroup: rowData["resourceGroup"].(string),
86 SubscriptionID: rowData["subscriptionId"].(string),
87 }
88 }
89
90 *requestOptions.Skip += ResourceGraphQueryOptionsTop
91
92 if result.TotalRecords != nil {
93 if int64(ptr.Deref(requestOptions.Skip)) >= ptr.Deref(result.TotalRecords) {
94 break
95 }
96 }
97 }
98
99 return zones, nil
100 }
101
102 func createGraphQuery(config *Config) string {
103 buf := new(bytes.Buffer)
104 buf.WriteString("\nresources\n")
105
106 resourceType := ResourceGraphTypePublicDNSZone
107 if config.PrivateZone {
108 resourceType = ResourceGraphTypePrivateDNSZone
109 }
110
111 _, _ = fmt.Fprintf(buf, "| where type =~ %q\n", resourceType)
112
113 if config.SubscriptionID != "" {
114 _, _ = fmt.Fprintf(buf, "| where subscriptionId =~ %q\n", config.SubscriptionID)
115 }
116
117 if config.ResourceGroup != "" {
118 _, _ = fmt.Fprintf(buf, "| where resourceGroup =~ %q\n", config.ResourceGroup)
119 }
120
121 if config.ServiceDiscoveryFilter != "" {
122 _, _ = fmt.Fprintf(buf, "| %s\n", config.ServiceDiscoveryFilter)
123 }
124
125 buf.WriteString("| project subscriptionId, resourceGroup, name")
126
127 return buf.String()
128 }
129