2019-09-19 11:45:14 +10:00
package azuredns
import (
"context"
"encoding/json"
"fmt"
2020-07-01 05:55:20 -04:00
"sort"
2019-11-01 00:14:42 +10:30
"strings"
"time"
2022-08-14 15:42:41 +04:00
aauth "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
adns "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/dns/armdns"
2019-09-19 11:45:14 +10:00
"github.com/Azure/go-autorest/autorest/to"
2023-05-20 19:21:45 +02:00
"github.com/StackExchange/dnscontrol/v4/models"
"github.com/StackExchange/dnscontrol/v4/pkg/diff"
"github.com/StackExchange/dnscontrol/v4/pkg/diff2"
"github.com/StackExchange/dnscontrol/v4/pkg/printer"
"github.com/StackExchange/dnscontrol/v4/pkg/txtutil"
"github.com/StackExchange/dnscontrol/v4/providers"
2019-09-19 11:45:14 +10:00
)
2020-10-26 09:25:30 -04:00
type azurednsProvider struct {
2020-03-02 20:25:42 +04:00
zonesClient * adns . ZonesClient
recordsClient * adns . RecordSetsClient
zones map [ string ] * adns . Zone
resourceGroup * string
2020-06-18 09:37:57 -04:00
subscriptionID * string
2023-04-14 15:22:23 -04:00
rawRecords map [ string ] [ ] * adns . RecordSet
zoneName map [ string ] string
2019-09-19 11:45:14 +10:00
}
2020-06-18 09:37:57 -04:00
func newAzureDNSDsp ( conf map [ string ] string , metadata json . RawMessage ) ( providers . DNSServiceProvider , error ) {
return newAzureDNS ( conf , metadata )
2019-09-19 11:45:14 +10:00
}
2020-10-26 09:25:30 -04:00
func newAzureDNS ( m map [ string ] string , metadata json . RawMessage ) ( * azurednsProvider , error ) {
2020-06-18 09:37:57 -04:00
subID , rg := m [ "SubscriptionID" ] , m [ "ResourceGroup" ]
2022-08-14 15:42:41 +04:00
clientID , clientSecret , tenantID := m [ "ClientID" ] , m [ "ClientSecret" ] , m [ "TenantID" ]
credential , authErr := aauth . NewClientSecretCredential ( tenantID , clientID , clientSecret , nil )
2019-09-19 11:45:14 +10:00
if authErr != nil {
return nil , authErr
}
2022-08-14 15:42:41 +04:00
zonesClient , zoneErr := adns . NewZonesClient ( subID , credential , nil )
if zoneErr != nil {
return nil , zoneErr
}
recordsClient , recordErr := adns . NewRecordSetsClient ( subID , credential , nil )
if recordErr != nil {
return nil , recordErr
}
2019-09-19 11:45:14 +10:00
2023-04-14 15:22:23 -04:00
api := & azurednsProvider {
zonesClient : zonesClient ,
recordsClient : recordsClient ,
resourceGroup : to . StringPtr ( rg ) ,
subscriptionID : to . StringPtr ( subID ) ,
rawRecords : map [ string ] [ ] * adns . RecordSet { } ,
zoneName : map [ string ] string { } ,
}
2019-09-19 11:45:14 +10:00
err := api . getZones ( )
if err != nil {
return nil , err
}
return api , nil
}
var features = providers . DocumentationNotes {
2022-03-02 11:19:15 -05:00
providers . CanGetZones : providers . Can ( ) ,
2020-03-02 20:25:42 +04:00
providers . CanUseAlias : providers . Cannot ( "Azure DNS does not provide a generic ALIAS functionality. Use AZURE_ALIAS instead." ) ,
2022-03-02 11:19:15 -05:00
providers . CanUseAzureAlias : providers . Can ( ) ,
2019-09-19 11:45:14 +10:00
providers . CanUseCAA : providers . Can ( ) ,
2023-03-16 19:04:20 +01:00
providers . CanUseLOC : providers . Cannot ( ) ,
2019-09-19 11:45:14 +10:00
providers . CanUseNAPTR : providers . Cannot ( ) ,
2022-03-02 11:19:15 -05:00
providers . CanUsePTR : providers . Can ( ) ,
providers . CanUseSRV : providers . Can ( ) ,
2019-09-19 11:45:14 +10:00
providers . CanUseSSHFP : providers . Cannot ( ) ,
providers . CanUseTLSA : providers . Cannot ( ) ,
2022-03-02 11:19:15 -05:00
providers . DocCreateDomains : providers . Can ( ) ,
providers . DocDualHost : providers . Can ( "Azure does not permit modifying the existing NS records, only adding/removing additional records." ) ,
providers . DocOfficiallySupported : providers . Can ( ) ,
2019-09-19 11:45:14 +10:00
}
func init ( ) {
2021-03-07 13:19:22 -05:00
fns := providers . DspFuncs {
2021-05-02 11:04:42 -04:00
Initializer : newAzureDNSDsp ,
2021-03-08 20:14:30 -05:00
RecordAuditor : AuditRecords ,
2021-03-07 13:19:22 -05:00
}
providers . RegisterDomainServiceProviderType ( "AZURE_DNS" , fns , features )
2020-03-02 20:25:42 +04:00
providers . RegisterCustomRecordType ( "AZURE_ALIAS" , "AZURE_DNS" , "" )
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
func ( a * azurednsProvider ) getExistingZones ( ) ( [ ] * adns . Zone , error ) {
2019-09-19 11:45:14 +10:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
2022-08-14 15:42:41 +04:00
zonesPager := a . zonesClient . NewListByResourceGroupPager ( * a . resourceGroup , nil )
var zones [ ] * adns . Zone
for zonesPager . More ( ) {
nextResult , zonesErr := zonesPager . NextPage ( ctx )
if zonesErr != nil {
return nil , zonesErr
}
zones = append ( zones , nextResult . Value ... )
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
return zones , nil
2020-02-18 20:35:13 +04:00
}
2020-10-26 09:25:30 -04:00
func ( a * azurednsProvider ) getZones ( ) error {
2020-02-18 20:35:13 +04:00
a . zones = make ( map [ string ] * adns . Zone )
2022-08-14 15:42:41 +04:00
zones , err := a . getExistingZones ( )
if err != nil {
return err
2020-02-18 20:35:13 +04:00
}
2022-08-14 15:42:41 +04:00
for _ , z := range zones {
zone := z
domain := strings . TrimSuffix ( * z . Name , "." )
a . zones [ domain ] = zone
2019-09-19 11:45:14 +10:00
}
return nil
}
type errNoExist struct {
domain string
}
func ( e errNoExist ) Error ( ) string {
return fmt . Sprintf ( "Domain %s not found in you Azure account" , e . domain )
}
2020-10-26 09:25:30 -04:00
func ( a * azurednsProvider ) GetNameservers ( domain string ) ( [ ] * models . Nameserver , error ) {
2019-09-19 11:45:14 +10:00
zone , ok := a . zones [ domain ]
if ! ok {
return nil , errNoExist { domain }
}
2020-03-01 10:33:24 -05:00
var nss [ ] string
2022-08-14 15:42:41 +04:00
if zone . Properties != nil {
for _ , ns := range zone . Properties . NameServers {
nss = append ( nss , * ns )
}
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
2020-03-01 10:33:24 -05:00
return models . ToNameserversStripTD ( nss )
2019-09-19 11:45:14 +10:00
}
2020-10-26 09:25:30 -04:00
func ( a * azurednsProvider ) ListZones ( ) ( [ ] string , error ) {
2022-08-14 15:42:41 +04:00
zonesResult , err := a . getExistingZones ( )
if err != nil {
return nil , err
2020-02-18 20:35:13 +04:00
}
2022-08-14 15:42:41 +04:00
var zones [ ] string
2020-02-18 20:35:13 +04:00
2022-08-14 15:42:41 +04:00
for _ , z := range zonesResult {
domain := strings . TrimSuffix ( * z . Name , "." )
zones = append ( zones , domain )
2020-02-18 20:35:13 +04:00
}
return zones , nil
}
// GetZoneRecords gets the records of a zone and returns them in RecordConfig format.
2023-05-02 13:04:59 -04:00
func ( a * azurednsProvider ) GetZoneRecords ( domain string , meta map [ string ] string ) ( models . Records , error ) {
2020-02-18 20:35:13 +04:00
existingRecords , _ , _ , err := a . getExistingRecords ( domain )
2019-09-19 11:45:14 +10:00
if err != nil {
return nil , err
}
2023-04-14 15:22:23 -04:00
2020-02-18 20:35:13 +04:00
return existingRecords , nil
}
2019-09-19 11:45:14 +10:00
2020-10-26 09:25:30 -04:00
func ( a * azurednsProvider ) getExistingRecords ( domain string ) ( models . Records , [ ] * adns . RecordSet , string , error ) {
2020-02-18 20:35:13 +04:00
zone , ok := a . zones [ domain ]
2019-09-19 11:45:14 +10:00
if ! ok {
2020-02-18 20:35:13 +04:00
return nil , nil , "" , errNoExist { domain }
2019-09-19 11:45:14 +10:00
}
2020-08-30 20:38:08 -04:00
zoneName := * zone . Name
2023-04-14 15:22:23 -04:00
rawRecords , err := a . fetchRecordSets ( zoneName )
2019-09-19 11:45:14 +10:00
if err != nil {
2020-02-18 20:35:13 +04:00
return nil , nil , "" , err
2019-09-19 11:45:14 +10:00
}
2020-02-18 20:35:13 +04:00
var existingRecords models . Records
2023-04-14 15:22:23 -04:00
for _ , set := range rawRecords {
2020-02-18 20:35:13 +04:00
existingRecords = append ( existingRecords , nativeToRecords ( set , zoneName ) ... )
2019-09-19 11:45:14 +10:00
}
2023-04-14 15:22:23 -04:00
a . rawRecords [ domain ] = rawRecords
a . zoneName [ domain ] = zoneName
2021-03-07 13:19:22 -05:00
2023-04-14 15:22:23 -04:00
return existingRecords , rawRecords , zoneName , nil
2020-02-18 20:35:13 +04:00
}
2023-04-14 15:22:23 -04:00
// GetZoneRecordsCorrections returns a list of corrections that will turn existing records into dc.Records.
func ( a * azurednsProvider ) GetZoneRecordsCorrections ( dc * models . DomainConfig , existingRecords models . Records ) ( [ ] * models . Correction , error ) {
txtutil . SplitSingleLongTxt ( existingRecords ) // Autosplit long TXT records
2021-03-07 13:19:22 -05:00
2022-12-11 15:02:58 -05:00
var corrections [ ] * models . Correction
2022-12-11 17:28:58 -05:00
if ! diff2 . EnableDiff2 {
2019-09-19 11:45:14 +10:00
2023-04-14 15:22:23 -04:00
records := a . rawRecords [ dc . Name ]
zoneName := a . zoneName [ dc . Name ]
2022-12-11 15:02:58 -05:00
differ := diff . New ( dc )
namesToUpdate , err := differ . ChangedGroups ( existingRecords )
if err != nil {
return nil , err
}
if len ( namesToUpdate ) == 0 {
return nil , nil
}
2019-09-19 11:45:14 +10:00
2022-12-11 15:02:58 -05:00
updates := map [ models . RecordKey ] [ ] * models . RecordConfig { }
2019-09-19 11:45:14 +10:00
2022-12-11 15:02:58 -05:00
for k := range namesToUpdate {
updates [ k ] = nil
for _ , rc := range dc . Records {
if rc . Key ( ) == k {
updates [ k ] = append ( updates [ k ] , rc )
}
2019-09-19 11:45:14 +10:00
}
}
2022-12-11 15:02:58 -05:00
for k , recs := range updates {
if len ( recs ) == 0 {
var rrset * adns . RecordSet
for _ , r := range records {
if strings . TrimSuffix ( * r . Properties . Fqdn , "." ) == k . NameFQDN {
n1 , err := nativeToRecordType ( r . Type )
if err != nil {
return nil , err
}
n2 , err := nativeToRecordType ( to . StringPtr ( k . Type ) )
if err != nil {
return nil , err
}
if n1 == n2 {
rrset = r
break
}
}
}
if rrset != nil {
corrections = append ( corrections ,
& models . Correction {
Msg : strings . Join ( namesToUpdate [ k ] , "\n" ) ,
F : func ( ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
rt , err := nativeToRecordType ( rrset . Type )
if err != nil {
return err
}
2023-02-07 16:53:49 -05:00
//fmt.Fprintf(os.Stderr, "DEBUG: 1 a.recordsClient.Delete(ctx, %v, %v, %v, %v)\n", *a.resourceGroup, zoneName, *rrset.Name, rt)
2022-12-11 15:02:58 -05:00
_ , err = a . recordsClient . Delete ( ctx , * a . resourceGroup , zoneName , * rrset . Name , rt , nil )
if err != nil {
return err
}
return nil
} ,
} )
} else {
return nil , fmt . Errorf ( "no record set found to delete. Name: '%s'. Type: '%s'" , k . NameFQDN , k . Type )
}
} else {
rrset , recordType , err := a . recordToNative ( k , recs )
if err != nil {
return nil , err
}
var recordName string
for _ , r := range recs {
i := int64 ( r . TTL )
rrset . Properties . TTL = & i // TODO: make sure that ttls are consistent within a set
recordName = r . Name
}
for _ , r := range records {
existingRecordType , err := nativeToRecordType ( r . Type )
2020-11-13 16:32:40 -05:00
if err != nil {
return nil , err
}
2022-12-11 15:02:58 -05:00
changedRecordType , err := nativeToRecordType ( to . StringPtr ( k . Type ) )
2020-11-13 16:32:40 -05:00
if err != nil {
return nil , err
}
2022-12-11 15:02:58 -05:00
if strings . TrimSuffix ( * r . Properties . Fqdn , "." ) == k . NameFQDN && ( changedRecordType == adns . RecordTypeCNAME || existingRecordType == adns . RecordTypeCNAME ) {
if existingRecordType == adns . RecordTypeA || existingRecordType == adns . RecordTypeAAAA || changedRecordType == adns . RecordTypeA || changedRecordType == adns . RecordTypeAAAA { //CNAME cannot coexist with an A or AA
corrections = append ( corrections ,
& models . Correction {
Msg : strings . Join ( namesToUpdate [ k ] , "\n" ) ,
F : func ( ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
2023-02-07 16:53:49 -05:00
//fmt.Fprintf(os.Stderr, "DEBUG: 2 a.recordsClient.Delete(ctx, %v, %v, %v, %v, nil)\n", *a.resourceGroup, zoneName, recordName, existingRecordType)
2022-12-11 15:02:58 -05:00
_ , err := a . recordsClient . Delete ( ctx , * a . resourceGroup , zoneName , recordName , existingRecordType , nil )
if err != nil {
return err
}
return nil
} ,
} )
}
2020-11-13 16:32:40 -05:00
}
2019-09-19 11:45:14 +10:00
}
2022-12-11 15:02:58 -05:00
2019-09-19 11:45:14 +10:00
corrections = append ( corrections ,
& models . Correction {
Msg : strings . Join ( namesToUpdate [ k ] , "\n" ) ,
F : func ( ) error {
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
2022-12-11 15:02:58 -05:00
_ , err := a . recordsClient . CreateOrUpdate ( ctx , * a . resourceGroup , zoneName , recordName , recordType , * rrset , nil )
2019-09-19 11:45:14 +10:00
if err != nil {
return err
}
return nil
} ,
} )
}
}
2022-12-11 15:02:58 -05:00
// Sort the records for cosmetic reasons: It just makes a long list
// of deletes or adds easier to read if they are in sorted order.
// That said, it may be risky to sort them (sort key is the text
// message "Msg") if there are deletes that must happen before adds.
// Reading the above code it isn't clear that any of the updates are
// order-dependent. That said, all the tests pass.
// If in the future this causes a bug, we can either just remove
// this next line, or (even better) put any order-dependent
// operations in a single models.Correction{}.
sort . Slice ( corrections , func ( i , j int ) bool { return diff . CorrectionLess ( corrections , i , j ) } )
return corrections , nil
2019-09-19 11:45:14 +10:00
}
2020-07-01 05:55:20 -04:00
2022-12-11 17:28:58 -05:00
// Azure is a "ByRSet" API.
2023-02-07 16:53:49 -05:00
changes , err := diff2 . ByRecordSet ( existingRecords , dc , nil )
2022-12-11 17:28:58 -05:00
if err != nil {
return nil , err
}
2023-02-07 16:53:49 -05:00
for _ , change := range changes {
// Copy all param values to local variables to avoid overwrites
msgs := change . MsgsJoined
dcn := dc . Name
chaKey := change . Key
2023-02-01 07:27:00 -05:00
2023-02-07 16:53:49 -05:00
switch change . Type {
case diff2 . REPORT :
corrections = append ( corrections , & models . Correction { Msg : change . MsgsJoined } )
2022-12-11 17:28:58 -05:00
case diff2 . CHANGE , diff2 . CREATE :
2023-02-07 16:53:49 -05:00
changeNew := change . New
corrections = append ( corrections , & models . Correction {
Msg : msgs ,
F : func ( ) error {
return a . recordCreate ( dcn , chaKey , changeNew )
} ,
} )
2022-12-11 17:28:58 -05:00
case diff2 . DELETE :
2023-02-07 16:53:49 -05:00
corrections = append ( corrections , & models . Correction {
Msg : msgs ,
F : func ( ) error {
//return a.recordDelete(dc.Name, change.Key, change.Old)
return a . recordDelete ( dcn , chaKey , change . Old )
} ,
} )
2023-02-01 07:27:00 -05:00
default :
2023-02-07 16:53:49 -05:00
panic ( fmt . Sprintf ( "unhandled change.Type %s" , change . Type ) )
2022-12-11 17:28:58 -05:00
}
}
2020-07-01 05:55:20 -04:00
2019-09-19 11:45:14 +10:00
return corrections , nil
}
2023-02-07 16:53:49 -05:00
func ( a * azurednsProvider ) recordCreate ( zoneName string , reckey models . RecordKey , recs models . Records ) error {
rrset , azRecType , err := a . recordToNativeDiff2 ( reckey , recs )
if err != nil {
return err
}
var recordName string
var i int64
for _ , r := range recs {
i = int64 ( r . TTL )
recordName = r . Name
}
rrset . Properties . TTL = & i
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
_ , err = a . recordsClient . CreateOrUpdate ( ctx , * a . resourceGroup , zoneName , recordName , azRecType , * rrset , nil )
return err
}
func ( a * azurednsProvider ) recordDelete ( zoneName string , reckey models . RecordKey , recs models . Records ) error {
shortName := strings . TrimSuffix ( reckey . NameFQDN , "." + zoneName )
if shortName == zoneName {
shortName = "@"
}
azRecType , err := nativeToRecordTypeDiff2 ( to . StringPtr ( reckey . Type ) )
if err != nil {
return nil
}
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
_ , err = a . recordsClient . Delete ( ctx , * a . resourceGroup , zoneName , shortName , azRecType , nil )
return err
}
2020-11-13 16:32:40 -05:00
func nativeToRecordType ( recordType * string ) ( adns . RecordType , error ) {
2019-12-10 05:51:23 +11:00
recordTypeStripped := strings . TrimPrefix ( * recordType , "Microsoft.Network/dnszones/" )
switch recordTypeStripped {
2020-03-02 20:25:42 +04:00
case "A" , "AZURE_ALIAS_A" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeA , nil
2020-03-02 20:25:42 +04:00
case "AAAA" , "AZURE_ALIAS_AAAA" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeAAAA , nil
2019-09-19 11:45:14 +10:00
case "CAA" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeCAA , nil
2020-03-02 20:25:42 +04:00
case "CNAME" , "AZURE_ALIAS_CNAME" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeCNAME , nil
2019-09-19 11:45:14 +10:00
case "MX" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeMX , nil
2019-09-19 11:45:14 +10:00
case "NS" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeNS , nil
2019-09-19 11:45:14 +10:00
case "PTR" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypePTR , nil
2019-09-19 11:45:14 +10:00
case "SRV" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeSRV , nil
2019-09-19 11:45:14 +10:00
case "TXT" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeTXT , nil
2019-09-19 11:45:14 +10:00
case "SOA" :
2022-08-14 15:42:41 +04:00
return adns . RecordTypeSOA , nil
2019-09-19 11:45:14 +10:00
default :
2020-11-13 16:32:40 -05:00
// Unimplemented type. Return adns.A as a decoy, but send an error.
2023-02-07 16:53:49 -05:00
return adns . RecordTypeA , fmt . Errorf ( "nativeToRecordType rtype %v unimplemented" , * recordType )
}
}
func nativeToRecordTypeDiff2 ( recordType * string ) ( adns . RecordType , error ) {
recordTypeStripped := strings . TrimPrefix ( * recordType , "Microsoft.Network/dnszones/" )
switch recordTypeStripped {
case "A" , "AZURE_ALIAS_A" :
return adns . RecordTypeA , nil
case "AAAA" , "AZURE_ALIAS_AAAA" :
return adns . RecordTypeAAAA , nil
case "CAA" :
return adns . RecordTypeCAA , nil
case "CNAME" , "AZURE_ALIAS_CNAME" :
return adns . RecordTypeCNAME , nil
case "MX" :
return adns . RecordTypeMX , nil
case "NS" :
return adns . RecordTypeNS , nil
case "PTR" :
return adns . RecordTypePTR , nil
case "SRV" :
return adns . RecordTypeSRV , nil
case "TXT" :
return adns . RecordTypeTXT , nil
case "SOA" :
return adns . RecordTypeSOA , nil
default :
// Unimplemented type. Return adns.A as a decoy, but send an error.
return adns . RecordTypeA , fmt . Errorf ( "nativeToRecordTypeDiff2 RTYPE %v UNIMPLEMENTED" , * recordType )
2019-09-19 11:45:14 +10:00
}
}
2023-01-09 12:51:18 -05:00
func safeTarget ( t * string ) string {
if t == nil {
return "foundnil"
}
return * t
}
2019-09-19 11:45:14 +10:00
func nativeToRecords ( set * adns . RecordSet , origin string ) [ ] * models . RecordConfig {
var results [ ] * models . RecordConfig
switch rtype := * set . Type ; rtype {
case "Microsoft.Network/dnszones/A" :
2022-08-14 15:42:41 +04:00
if set . Properties . ARecords != nil {
2023-02-07 16:53:49 -05:00
// This is an A recordset. Process all the targets there.
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . ARecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2020-02-08 06:25:08 +11:00
rc . Type = "A"
2022-08-14 15:42:41 +04:00
_ = rc . SetTarget ( * rec . IPv4Address )
2020-02-08 06:25:08 +11:00
results = append ( results , rc )
}
2020-03-02 20:25:42 +04:00
} else {
2023-02-07 16:53:49 -05:00
// This is an AZURE_ALIAS of an "A" record.
2020-03-02 20:25:42 +04:00
rc := & models . RecordConfig {
Type : "AZURE_ALIAS" ,
2022-08-14 15:42:41 +04:00
TTL : uint32 ( * set . Properties . TTL ) ,
2020-03-02 20:25:42 +04:00
AzureAlias : map [ string ] string {
"type" : "A" ,
} ,
2022-12-11 17:28:58 -05:00
Original : set ,
2020-03-02 20:25:42 +04:00
}
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2023-01-09 12:51:18 -05:00
_ = rc . SetTarget ( safeTarget ( set . Properties . TargetResource . ID ) )
2020-03-02 20:25:42 +04:00
results = append ( results , rc )
2019-09-19 11:45:14 +10:00
}
case "Microsoft.Network/dnszones/AAAA" :
2022-08-14 15:42:41 +04:00
if set . Properties . AaaaRecords != nil {
2023-02-07 16:53:49 -05:00
// This is an AAAA recordset. Process all the targets there.
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . AaaaRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2020-02-08 06:25:08 +11:00
rc . Type = "AAAA"
2022-08-14 15:42:41 +04:00
_ = rc . SetTarget ( * rec . IPv6Address )
2020-02-08 06:25:08 +11:00
results = append ( results , rc )
}
2020-03-02 20:25:42 +04:00
} else {
2023-02-07 16:53:49 -05:00
// This is an AZURE_ALIAS of an "AAAA" record.
2020-03-02 20:25:42 +04:00
rc := & models . RecordConfig {
Type : "AZURE_ALIAS" ,
2022-08-14 15:42:41 +04:00
TTL : uint32 ( * set . Properties . TTL ) ,
2020-03-02 20:25:42 +04:00
AzureAlias : map [ string ] string {
"type" : "AAAA" ,
} ,
2022-12-11 17:28:58 -05:00
Original : set ,
2020-03-02 20:25:42 +04:00
}
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2023-01-09 12:51:18 -05:00
_ = rc . SetTarget ( safeTarget ( set . Properties . TargetResource . ID ) )
2020-03-02 20:25:42 +04:00
results = append ( results , rc )
2019-09-19 11:45:14 +10:00
}
case "Microsoft.Network/dnszones/CNAME" :
2022-08-14 15:42:41 +04:00
if set . Properties . CnameRecord != nil {
2023-02-07 16:53:49 -05:00
// This is a CNAME recordset. Process the targets. (there can only be one)
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2020-03-02 20:25:42 +04:00
rc . Type = "CNAME"
2022-08-14 15:42:41 +04:00
_ = rc . SetTarget ( * set . Properties . CnameRecord . Cname )
2020-03-02 20:25:42 +04:00
results = append ( results , rc )
} else {
2023-02-07 16:53:49 -05:00
// This is an AZURE_ALIAS of a "CNAME" record.
2020-03-02 20:25:42 +04:00
rc := & models . RecordConfig {
Type : "AZURE_ALIAS" ,
2022-08-14 15:42:41 +04:00
TTL : uint32 ( * set . Properties . TTL ) ,
2020-03-02 20:25:42 +04:00
AzureAlias : map [ string ] string {
"type" : "CNAME" ,
} ,
2022-12-11 17:28:58 -05:00
Original : set ,
2020-03-02 20:25:42 +04:00
}
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2023-01-09 12:51:18 -05:00
_ = rc . SetTarget ( safeTarget ( set . Properties . TargetResource . ID ) )
2020-03-02 20:25:42 +04:00
results = append ( results , rc )
}
2019-09-19 11:45:14 +10:00
case "Microsoft.Network/dnszones/NS" :
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . NsRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2019-09-19 11:45:14 +10:00
rc . Type = "NS"
_ = rc . SetTarget ( * rec . Nsdname )
results = append ( results , rc )
}
case "Microsoft.Network/dnszones/PTR" :
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . PtrRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2019-09-19 11:45:14 +10:00
rc . Type = "PTR"
_ = rc . SetTarget ( * rec . Ptrdname )
results = append ( results , rc )
}
case "Microsoft.Network/dnszones/TXT" :
2022-08-14 15:42:41 +04:00
if len ( set . Properties . TxtRecords ) == 0 { // Empty String Record Parsing
2023-02-07 16:53:49 -05:00
// This is a null TXT record.
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2019-09-19 11:45:14 +10:00
rc . Type = "TXT"
2020-02-18 17:45:31 +04:00
_ = rc . SetTargetTXT ( "" )
2019-09-19 11:45:14 +10:00
results = append ( results , rc )
2020-02-18 17:45:31 +04:00
} else {
2023-02-07 16:53:49 -05:00
// This is a normal TXT record. Collect all its segments.
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . TxtRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2020-02-18 17:45:31 +04:00
rc . Type = "TXT"
2022-08-14 15:42:41 +04:00
var txts [ ] string
for _ , txt := range rec . Value {
txts = append ( txts , * txt )
}
_ = rc . SetTargetTXTs ( txts )
2020-02-18 17:45:31 +04:00
results = append ( results , rc )
}
2019-09-19 11:45:14 +10:00
}
case "Microsoft.Network/dnszones/MX" :
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . MxRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2019-09-19 11:45:14 +10:00
rc . Type = "MX"
_ = rc . SetTargetMX ( uint16 ( * rec . Preference ) , * rec . Exchange )
results = append ( results , rc )
}
case "Microsoft.Network/dnszones/SRV" :
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . SrvRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2019-09-19 11:45:14 +10:00
rc . Type = "SRV"
_ = rc . SetTargetSRV ( uint16 ( * rec . Priority ) , uint16 ( * rec . Weight ) , uint16 ( * rec . Port ) , * rec . Target )
results = append ( results , rc )
}
case "Microsoft.Network/dnszones/CAA" :
2022-08-14 15:42:41 +04:00
for _ , rec := range set . Properties . CaaRecords {
2022-12-11 17:28:58 -05:00
rc := & models . RecordConfig { TTL : uint32 ( * set . Properties . TTL ) , Original : set }
2022-08-14 15:42:41 +04:00
rc . SetLabelFromFQDN ( * set . Properties . Fqdn , origin )
2019-09-19 11:45:14 +10:00
rc . Type = "CAA"
_ = rc . SetTargetCAA ( uint8 ( * rec . Flags ) , * rec . Tag , * rec . Value )
results = append ( results , rc )
}
case "Microsoft.Network/dnszones/SOA" :
default :
2023-02-07 16:53:49 -05:00
panic ( fmt . Errorf ( "nativeToRecords rtype %v unimplemented" , * set . Type ) )
2019-09-19 11:45:14 +10:00
}
return results
}
2020-11-13 16:32:40 -05:00
func ( a * azurednsProvider ) recordToNative ( recordKey models . RecordKey , recordConfig [ ] * models . RecordConfig ) ( * adns . RecordSet , adns . RecordType , error ) {
2022-08-14 15:42:41 +04:00
recordSet := & adns . RecordSet { Type : to . StringPtr ( recordKey . Type ) , Properties : & adns . RecordSetProperties { } }
2019-09-19 11:45:14 +10:00
for _ , rec := range recordConfig {
switch recordKey . Type {
case "A" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . ARecords == nil {
recordSet . Properties . ARecords = [ ] * adns . ARecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . ARecords = append ( recordSet . Properties . ARecords , & adns . ARecord { IPv4Address : to . StringPtr ( rec . GetTargetField ( ) ) } )
2019-09-19 11:45:14 +10:00
case "AAAA" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . AaaaRecords == nil {
recordSet . Properties . AaaaRecords = [ ] * adns . AaaaRecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . AaaaRecords = append ( recordSet . Properties . AaaaRecords , & adns . AaaaRecord { IPv6Address : to . StringPtr ( rec . GetTargetField ( ) ) } )
2019-09-19 11:45:14 +10:00
case "CNAME" :
2022-08-14 15:42:41 +04:00
recordSet . Properties . CnameRecord = & adns . CnameRecord { Cname : to . StringPtr ( rec . GetTargetField ( ) ) }
2019-09-19 11:45:14 +10:00
case "NS" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . NsRecords == nil {
recordSet . Properties . NsRecords = [ ] * adns . NsRecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . NsRecords = append ( recordSet . Properties . NsRecords , & adns . NsRecord { Nsdname : to . StringPtr ( rec . GetTargetField ( ) ) } )
2019-09-19 11:45:14 +10:00
case "PTR" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . PtrRecords == nil {
recordSet . Properties . PtrRecords = [ ] * adns . PtrRecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . PtrRecords = append ( recordSet . Properties . PtrRecords , & adns . PtrRecord { Ptrdname : to . StringPtr ( rec . GetTargetField ( ) ) } )
2019-09-19 11:45:14 +10:00
case "TXT" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . TxtRecords == nil {
recordSet . Properties . TxtRecords = [ ] * adns . TxtRecord { }
2019-09-19 11:45:14 +10:00
}
2020-02-18 17:45:31 +04:00
// Empty TXT record needs to have no value set in it's properties
if ! ( len ( rec . TxtStrings ) == 1 && rec . TxtStrings [ 0 ] == "" ) {
2022-08-14 15:42:41 +04:00
var txts [ ] * string
for _ , txt := range rec . TxtStrings {
txts = append ( txts , to . StringPtr ( txt ) )
}
recordSet . Properties . TxtRecords = append ( recordSet . Properties . TxtRecords , & adns . TxtRecord { Value : txts } )
2020-02-18 17:45:31 +04:00
}
2019-09-19 11:45:14 +10:00
case "MX" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . MxRecords == nil {
recordSet . Properties . MxRecords = [ ] * adns . MxRecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . MxRecords = append ( recordSet . Properties . MxRecords , & adns . MxRecord { Exchange : to . StringPtr ( rec . GetTargetField ( ) ) , Preference : to . Int32Ptr ( int32 ( rec . MxPreference ) ) } )
2019-09-19 11:45:14 +10:00
case "SRV" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . SrvRecords == nil {
recordSet . Properties . SrvRecords = [ ] * adns . SrvRecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . SrvRecords = append ( recordSet . Properties . SrvRecords , & adns . SrvRecord { Target : to . StringPtr ( rec . GetTargetField ( ) ) , Port : to . Int32Ptr ( int32 ( rec . SrvPort ) ) , Weight : to . Int32Ptr ( int32 ( rec . SrvWeight ) ) , Priority : to . Int32Ptr ( int32 ( rec . SrvPriority ) ) } )
2019-09-19 11:45:14 +10:00
case "CAA" :
2022-08-14 15:42:41 +04:00
if recordSet . Properties . CaaRecords == nil {
recordSet . Properties . CaaRecords = [ ] * adns . CaaRecord { }
2019-09-19 11:45:14 +10:00
}
2022-08-14 15:42:41 +04:00
recordSet . Properties . CaaRecords = append ( recordSet . Properties . CaaRecords , & adns . CaaRecord { Value : to . StringPtr ( rec . GetTargetField ( ) ) , Tag : to . StringPtr ( rec . CaaTag ) , Flags : to . Int32Ptr ( int32 ( rec . CaaFlag ) ) } )
2020-03-02 20:25:42 +04:00
case "AZURE_ALIAS_A" , "AZURE_ALIAS_AAAA" , "AZURE_ALIAS_CNAME" :
* recordSet . Type = rec . AzureAlias [ "type" ]
2022-08-14 15:42:41 +04:00
recordSet . Properties . TargetResource = & adns . SubResource { ID : to . StringPtr ( rec . GetTargetField ( ) ) }
2019-09-19 11:45:14 +10:00
default :
2023-02-07 16:53:49 -05:00
return nil , adns . RecordTypeA , fmt . Errorf ( "recordToNative rtype %v unimplemented" , recordKey . Type ) // ands.A is a placeholder
2019-09-19 11:45:14 +10:00
}
}
2020-03-02 20:25:42 +04:00
2020-11-13 16:32:40 -05:00
rt , err := nativeToRecordType ( to . StringPtr ( * recordSet . Type ) )
if err != nil {
2022-08-14 15:42:41 +04:00
return nil , adns . RecordTypeA , err // adns.A is a placeholder
2020-11-13 16:32:40 -05:00
}
return recordSet , rt , nil
2023-02-07 16:53:49 -05:00
}
// NOTE recordToNativeDiff2 is really "convert []RecordConfig to rrset".
func ( a * azurednsProvider ) recordToNativeDiff2 ( recordKey models . RecordKey , recordConfig [ ] * models . RecordConfig ) ( * adns . RecordSet , adns . RecordType , error ) {
recordKeyType := recordKey . Type
// if recordKeyType == "AZURE_ALIAS" {
// fmt.Fprintf(os.Stderr, "DEBUG: XXXXXXXXXXXXXXXXXXXXXXX %v\n", recordKeyType)
// }
recordSet := & adns . RecordSet { Type : to . StringPtr ( recordKeyType ) , Properties : & adns . RecordSetProperties { } }
for _ , rec := range recordConfig {
switch recordKeyType {
case "A" :
if recordSet . Properties . ARecords == nil {
recordSet . Properties . ARecords = [ ] * adns . ARecord { }
}
recordSet . Properties . ARecords = append ( recordSet . Properties . ARecords , & adns . ARecord { IPv4Address : to . StringPtr ( rec . GetTargetField ( ) ) } )
case "AAAA" :
if recordSet . Properties . AaaaRecords == nil {
recordSet . Properties . AaaaRecords = [ ] * adns . AaaaRecord { }
}
recordSet . Properties . AaaaRecords = append ( recordSet . Properties . AaaaRecords , & adns . AaaaRecord { IPv6Address : to . StringPtr ( rec . GetTargetField ( ) ) } )
case "CNAME" :
recordSet . Properties . CnameRecord = & adns . CnameRecord { Cname : to . StringPtr ( rec . GetTargetField ( ) ) }
case "NS" :
if recordSet . Properties . NsRecords == nil {
recordSet . Properties . NsRecords = [ ] * adns . NsRecord { }
}
recordSet . Properties . NsRecords = append ( recordSet . Properties . NsRecords , & adns . NsRecord { Nsdname : to . StringPtr ( rec . GetTargetField ( ) ) } )
case "PTR" :
if recordSet . Properties . PtrRecords == nil {
recordSet . Properties . PtrRecords = [ ] * adns . PtrRecord { }
}
recordSet . Properties . PtrRecords = append ( recordSet . Properties . PtrRecords , & adns . PtrRecord { Ptrdname : to . StringPtr ( rec . GetTargetField ( ) ) } )
case "TXT" :
if recordSet . Properties . TxtRecords == nil {
recordSet . Properties . TxtRecords = [ ] * adns . TxtRecord { }
}
// Empty TXT record needs to have no value set in it's properties
if ! ( len ( rec . TxtStrings ) == 1 && rec . TxtStrings [ 0 ] == "" ) {
var txts [ ] * string
for _ , txt := range rec . TxtStrings {
txts = append ( txts , to . StringPtr ( txt ) )
}
recordSet . Properties . TxtRecords = append ( recordSet . Properties . TxtRecords , & adns . TxtRecord { Value : txts } )
}
case "MX" :
if recordSet . Properties . MxRecords == nil {
recordSet . Properties . MxRecords = [ ] * adns . MxRecord { }
}
recordSet . Properties . MxRecords = append ( recordSet . Properties . MxRecords , & adns . MxRecord { Exchange : to . StringPtr ( rec . GetTargetField ( ) ) , Preference : to . Int32Ptr ( int32 ( rec . MxPreference ) ) } )
case "SRV" :
if recordSet . Properties . SrvRecords == nil {
recordSet . Properties . SrvRecords = [ ] * adns . SrvRecord { }
}
recordSet . Properties . SrvRecords = append ( recordSet . Properties . SrvRecords , & adns . SrvRecord { Target : to . StringPtr ( rec . GetTargetField ( ) ) , Port : to . Int32Ptr ( int32 ( rec . SrvPort ) ) , Weight : to . Int32Ptr ( int32 ( rec . SrvWeight ) ) , Priority : to . Int32Ptr ( int32 ( rec . SrvPriority ) ) } )
case "CAA" :
if recordSet . Properties . CaaRecords == nil {
recordSet . Properties . CaaRecords = [ ] * adns . CaaRecord { }
}
recordSet . Properties . CaaRecords = append ( recordSet . Properties . CaaRecords , & adns . CaaRecord { Value : to . StringPtr ( rec . GetTargetField ( ) ) , Tag : to . StringPtr ( rec . CaaTag ) , Flags : to . Int32Ptr ( int32 ( rec . CaaFlag ) ) } )
case "AZURE_ALIAS_A" , "AZURE_ALIAS_AAAA" , "AZURE_ALIAS_CNAME" :
aatype := rec . AzureAlias [ "type" ]
recordSet . Type = & aatype
aatarg := to . StringPtr ( rec . GetTargetField ( ) )
aasub := adns . SubResource { ID : aatarg }
recordSet . Properties . TargetResource = & aasub
default :
return nil , adns . RecordTypeA , fmt . Errorf ( "recordToNativeDiff2 RTYPE %v UNIMPLEMENTED" , recordKeyType ) // ands.A is a placeholder
}
}
rt , err := nativeToRecordTypeDiff2 ( to . StringPtr ( * recordSet . Type ) )
if err != nil {
return nil , adns . RecordTypeA , err // adns.A is a placeholder
}
return recordSet , rt , nil
2019-09-19 11:45:14 +10:00
}
2020-10-26 09:25:30 -04:00
func ( a * azurednsProvider ) fetchRecordSets ( zoneName string ) ( [ ] * adns . RecordSet , error ) {
2020-02-06 10:19:24 +11:00
if zoneName == "" {
2019-09-19 11:45:14 +10:00
return nil , nil
}
var records [ ] * adns . RecordSet
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
2022-08-14 15:42:41 +04:00
recordsPager := a . recordsClient . NewListAllByDNSZonePager ( * a . resourceGroup , zoneName , nil )
2019-09-19 11:45:14 +10:00
2022-08-14 15:42:41 +04:00
for recordsPager . More ( ) {
nextResult , recordsErr := recordsPager . NextPage ( ctx )
if recordsErr != nil {
return nil , recordsErr
2020-07-28 20:35:56 +03:00
}
2022-08-14 15:42:41 +04:00
records = append ( records , nextResult . Value ... )
2019-09-19 11:45:14 +10:00
}
return records , nil
}
2023-02-07 17:52:49 +05:30
func ( a * azurednsProvider ) EnsureZoneExists ( domain string ) error {
2019-09-19 11:45:14 +10:00
if _ , ok := a . zones [ domain ] ; ok {
return nil
}
2022-06-18 15:01:02 +02:00
printer . Printf ( "Adding zone for %s to Azure dns account\n" , domain )
2019-09-19 11:45:14 +10:00
ctx , cancel := context . WithTimeout ( context . Background ( ) , 6000 * time . Second )
defer cancel ( )
2022-08-14 15:42:41 +04:00
_ , err := a . zonesClient . CreateOrUpdate ( ctx , * a . resourceGroup , domain , adns . Zone { Location : to . StringPtr ( "global" ) } , nil )
2019-09-19 11:45:14 +10:00
if err != nil {
return err
}
return nil
}