mirror of
https://github.com/StackExchange/dnscontrol.git
synced 2024-05-11 05:55:12 +00:00
NEW PROVIDER: MSDNS (#1005)
* New provider * Add support for SRV records * Modify ACTIVEDIRECTORY_PS provider to warn that it is deprecated.
This commit is contained in:
122
providers/msdns/convert.go
Normal file
122
providers/msdns/convert.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package msdns
|
||||
|
||||
// Convert the provider's native record description to models.RecordConfig.
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
)
|
||||
|
||||
// extractProps and collects Name/Value pairs into maps for easier access.
|
||||
func extractProps(cip []ciProperty) (map[string]string, map[string]uint32, error) {
|
||||
|
||||
// Sadly this structure is dynamic JSON i.e. .Value could be an int, string,
|
||||
// or a map. We peek at the first byte to guess at the contents.
|
||||
|
||||
// We store strings in sprops, numbers in uprops. Maps are special: Currently
|
||||
// the only map we decode is a map with the same duration in many units. We
|
||||
// simply pick the Seconds unit and store it as a number.
|
||||
|
||||
sprops := map[string]string{}
|
||||
uprops := map[string]uint32{}
|
||||
for _, p := range cip {
|
||||
name := p.Name
|
||||
if len(p.Value) == 0 {
|
||||
// Empty string? Skip it.
|
||||
} else if p.Value[0] == '"' {
|
||||
// First byte is a quote. Must be a string.
|
||||
var svalue string
|
||||
err := json.Unmarshal(p.Value, &svalue)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not unmarshal string value=%q: %w", p.Value, err)
|
||||
}
|
||||
sprops[name] = svalue
|
||||
} else if p.Value[0] == '{' {
|
||||
// First byte is {. Must be a map.
|
||||
var dvalue ciValueDuration
|
||||
err := json.Unmarshal(p.Value, &dvalue)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not unmarshal duration value=%q: %w", p.Value, err)
|
||||
}
|
||||
uprops[name] = uint32(dvalue.TotalSeconds)
|
||||
} else {
|
||||
// Assume it is a number.
|
||||
var uvalue uint32
|
||||
err := json.Unmarshal(p.Value, &uvalue)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("could not unmarshal uint value=%q: %w", p.Value, err)
|
||||
}
|
||||
uprops[name] = uvalue
|
||||
}
|
||||
}
|
||||
return sprops, uprops, nil
|
||||
}
|
||||
|
||||
// nativeToRecord takes a DNS record from DNS and returns a native RecordConfig struct.
|
||||
func nativeToRecords(nr nativeRecord, origin string) (*models.RecordConfig, error) {
|
||||
rc := &models.RecordConfig{
|
||||
Type: nr.RecordType,
|
||||
Original: nr,
|
||||
}
|
||||
rc.SetLabel(nr.HostName, origin)
|
||||
rc.TTL = uint32(nr.TimeToLive.TotalSeconds)
|
||||
|
||||
sprops, uprops, err := extractProps(nr.RecordData.CimInstanceProperties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch rtype := nr.RecordType; rtype {
|
||||
case "A":
|
||||
contents := sprops["IPv4Address"]
|
||||
ip := net.ParseIP(contents)
|
||||
if ip == nil || ip.To4() == nil {
|
||||
return nil, fmt.Errorf("invalid IP in A record: %q", contents)
|
||||
}
|
||||
rc.SetTargetIP(ip)
|
||||
case "AAAA":
|
||||
contents := sprops["IPv6Address"]
|
||||
ip := net.ParseIP(contents)
|
||||
if ip == nil || ip.To16() == nil {
|
||||
return nil, fmt.Errorf("invalid IPv6 in AAAA record: %q", contents)
|
||||
}
|
||||
rc.SetTargetIP(ip)
|
||||
case "CNAME":
|
||||
rc.SetTarget(sprops["HostNameAlias"])
|
||||
case "MX":
|
||||
rc.SetTargetMX(uint16(uprops["Preference"]), sprops["MailExchange"])
|
||||
case "NS":
|
||||
rc.SetTarget(sprops["NameServer"])
|
||||
case "PTR":
|
||||
rc.SetTarget(sprops["PtrDomainName"])
|
||||
case "SRV":
|
||||
rc.SetTargetSRV(
|
||||
uint16(uprops["Priority"]),
|
||||
uint16(uprops["Weight"]),
|
||||
uint16(uprops["Port"]),
|
||||
sprops["DomainName"],
|
||||
)
|
||||
case "SOA":
|
||||
// We discard SOA records for now. Windows DNS doesn't let us delete
|
||||
// them and they get in the way of integration tests. In the future,
|
||||
// we should support SOA records by (1) ignoring them in the
|
||||
// integration tests. (2) generatePSModify will have to special-case
|
||||
// updates.
|
||||
return nil, nil
|
||||
// If we weren't ignoring them, the code would look like this:
|
||||
//rc.SetTargetSOA(sprops["PrimaryServer"], sprops["ResponsiblePerson"],
|
||||
// uprops["SerialNumber"], uprops["RefreshInterval"], uprops["RetryDelay"],
|
||||
// uprops["ExpireLimit"], uprops["MinimumTimeToLive"])
|
||||
case "TXT":
|
||||
rc.SetTargetTXTString(sprops["DescriptiveText"])
|
||||
default:
|
||||
return nil, fmt.Errorf(
|
||||
"msdns/convert.go:nativeToRecord rtype=%q unknown: props=%+v and %+v",
|
||||
rtype, sprops, uprops)
|
||||
}
|
||||
|
||||
return rc, nil
|
||||
}
|
72
providers/msdns/corrections.go
Normal file
72
providers/msdns/corrections.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package msdns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/pkg/diff"
|
||||
)
|
||||
|
||||
// GetDomainCorrections gets existing records, diffs them against existing, and returns corrections.
|
||||
func (c *msdnsProvider) GenerateDomainCorrections(dc *models.DomainConfig, existing models.Records) ([]*models.Correction, error) {
|
||||
|
||||
// Read foundRecords:
|
||||
foundRecords, err := c.GetZoneRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("c.GetDNSZoneRecords(%v) failed: %v", dc.Name, err)
|
||||
}
|
||||
|
||||
// Normalize
|
||||
models.PostProcessRecords(foundRecords)
|
||||
|
||||
differ := diff.New(dc)
|
||||
_, creates, dels, modifications, err := differ.IncrementalDiff(foundRecords)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate changes.
|
||||
corrections := []*models.Correction{}
|
||||
for _, del := range dels {
|
||||
corrections = append(corrections, c.deleteRec(c.dnsserver, dc.Name, del))
|
||||
}
|
||||
for _, cre := range creates {
|
||||
corrections = append(corrections, c.createRec(c.dnsserver, dc.Name, cre)...)
|
||||
}
|
||||
for _, m := range modifications {
|
||||
corrections = append(corrections, c.modifyRec(c.dnsserver, dc.Name, m))
|
||||
}
|
||||
return corrections, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *msdnsProvider) deleteRec(dnsserver, domainname string, cor diff.Correlation) *models.Correction {
|
||||
rec := cor.Existing
|
||||
return &models.Correction{
|
||||
Msg: cor.String(),
|
||||
F: func() error {
|
||||
return c.shell.RecordDelete(dnsserver, domainname, rec)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *msdnsProvider) createRec(dnsserver, domainname string, cre diff.Correlation) []*models.Correction {
|
||||
rec := cre.Desired
|
||||
arr := []*models.Correction{{
|
||||
Msg: cre.String(),
|
||||
F: func() error {
|
||||
return c.shell.RecordCreate(dnsserver, domainname, rec)
|
||||
},
|
||||
}}
|
||||
return arr
|
||||
}
|
||||
|
||||
func (c *msdnsProvider) modifyRec(dnsserver, domainname string, m diff.Correlation) *models.Correction {
|
||||
old, rec := m.Existing, m.Desired
|
||||
return &models.Correction{
|
||||
Msg: m.String(),
|
||||
F: func() error {
|
||||
return c.shell.RecordModify(dnsserver, domainname, old, rec)
|
||||
},
|
||||
}
|
||||
}
|
8
providers/msdns/domains.go
Normal file
8
providers/msdns/domains.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package msdns
|
||||
|
||||
import "github.com/StackExchange/dnscontrol/v3/models"
|
||||
|
||||
func (c *msdnsProvider) GetNameservers(string) ([]*models.Nameserver, error) {
|
||||
// TODO: If using AD for publicly hosted zones, probably pull these from config.
|
||||
return nil, nil
|
||||
}
|
9
providers/msdns/listzones.go
Normal file
9
providers/msdns/listzones.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package msdns
|
||||
|
||||
func (c *msdnsProvider) ListZones() ([]string, error) {
|
||||
zones, err := c.shell.GetDNSServerZoneAll(c.dnsserver)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return zones, err
|
||||
}
|
129
providers/msdns/msdnsProvider.go
Normal file
129
providers/msdns/msdnsProvider.go
Normal file
@@ -0,0 +1,129 @@
|
||||
package msdns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/StackExchange/dnscontrol/v3/providers"
|
||||
)
|
||||
|
||||
// This is the struct that matches either (or both) of the Registrar and/or DNSProvider interfaces:
|
||||
type msdnsProvider struct {
|
||||
dnsserver string // Which DNS Server to update
|
||||
pssession string // Remote machine to PSSession to
|
||||
shell DNSAccessor // Handle for
|
||||
}
|
||||
|
||||
var features = providers.DocumentationNotes{
|
||||
providers.CanGetZones: providers.Can(),
|
||||
providers.CanUseAlias: providers.Cannot(),
|
||||
providers.CanUseCAA: providers.Cannot(),
|
||||
providers.CanUseDS: providers.Unimplemented(),
|
||||
providers.CanUsePTR: providers.Can(),
|
||||
providers.CanUseSRV: providers.Can(),
|
||||
providers.CanUseTLSA: providers.Unimplemented(),
|
||||
providers.CanUseTXTMulti: providers.Unimplemented(),
|
||||
providers.DocCreateDomains: providers.Cannot("This provider assumes the zone already existing on the dns server"),
|
||||
providers.DocDualHost: providers.Cannot("This driver does not manage NS records, so should not be used for dual-host scenarios"),
|
||||
providers.DocOfficiallySupported: providers.Can(),
|
||||
}
|
||||
|
||||
// Register with the dnscontrol system.
|
||||
// This establishes the name (all caps), and the function to call to initialize it.
|
||||
func init() {
|
||||
providers.RegisterDomainServiceProviderType("MSDNS", newDNS, features)
|
||||
}
|
||||
|
||||
func newDNS(config map[string]string, metadata json.RawMessage) (providers.DNSServiceProvider, error) {
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
fmt.Println("INFO: PowerShell not available. Disabling Active Directory provider.")
|
||||
return providers.None{}, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
p := &msdnsProvider{
|
||||
dnsserver: config["dnsserver"],
|
||||
}
|
||||
p.shell, err = newPowerShell(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Section 3: Domain Service Provider (DSP) related functions
|
||||
|
||||
// NB(tal): To future-proof your code, all new providers should
|
||||
// implement GetDomainCorrections exactly as you see here
|
||||
// (byte-for-byte the same). In 3.0
|
||||
// we plan on using just the individual calls to GetZoneRecords,
|
||||
// PostProcessRecords, and so on.
|
||||
//
|
||||
// Currently every provider does things differently, which prevents
|
||||
// us from doing things like using GetZoneRecords() of a provider
|
||||
// to make convertzone work with all providers.
|
||||
|
||||
// GetDomainCorrections get the current and existing records,
|
||||
// post-process them, and generate corrections.
|
||||
func (client *msdnsProvider) GetDomainCorrections(dc *models.DomainConfig) ([]*models.Correction, error) {
|
||||
existing, err := client.GetZoneRecords(dc.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
models.PostProcessRecords(existing)
|
||||
clean := PrepFoundRecords(existing)
|
||||
PrepDesiredRecords(dc)
|
||||
return client.GenerateDomainCorrections(dc, clean)
|
||||
}
|
||||
|
||||
// GetZoneRecords gathers the DNS records and converts them to
|
||||
// dnscontrol's format.
|
||||
func (client *msdnsProvider) GetZoneRecords(domain string) (models.Records, error) {
|
||||
|
||||
// Get the existing DNS records in native format.
|
||||
nativeExistingRecords, err := client.shell.GetDNSZoneRecords(client.dnsserver, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert them to DNScontrol's native format:
|
||||
existingRecords := make([]*models.RecordConfig, 0, len(nativeExistingRecords))
|
||||
for _, rr := range nativeExistingRecords {
|
||||
rc, err := nativeToRecords(rr, domain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if rc != nil {
|
||||
existingRecords = append(existingRecords, rc)
|
||||
}
|
||||
}
|
||||
|
||||
return existingRecords, nil
|
||||
}
|
||||
|
||||
// PrepFoundRecords munges any records to make them compatible with
|
||||
// this provider. Usually this is a no-op.
|
||||
func PrepFoundRecords(recs models.Records) models.Records {
|
||||
// If there are records that need to be modified, removed, etc. we
|
||||
// do it here. Usually this is a no-op.
|
||||
return recs
|
||||
}
|
||||
|
||||
// PrepDesiredRecords munges any records to best suit this provider.
|
||||
func PrepDesiredRecords(dc *models.DomainConfig) {
|
||||
// Sort through the dc.Records, eliminate any that can't be
|
||||
// supported; modify any that need adjustments to work with the
|
||||
// provider. We try to do minimal changes otherwise it gets
|
||||
// confusing.
|
||||
|
||||
dc.Punycode()
|
||||
}
|
||||
|
||||
// NB(tlim): If we want to implement a registrar, refer to
|
||||
// http://go.microsoft.com/fwlink/?LinkId=288158
|
||||
// (Get-DnsServerZoneDelegation) for hints about which PowerShell
|
||||
// commands to use.
|
414
providers/msdns/powershell.go
Normal file
414
providers/msdns/powershell.go
Normal file
@@ -0,0 +1,414 @@
|
||||
package msdns
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
"github.com/TomOnTime/utfutil"
|
||||
ps "github.com/bhendo/go-powershell"
|
||||
"github.com/bhendo/go-powershell/backend"
|
||||
"github.com/bhendo/go-powershell/middleware"
|
||||
)
|
||||
|
||||
type psHandle struct {
|
||||
shell ps.Shell
|
||||
}
|
||||
|
||||
func newPowerShell(config map[string]string) (*psHandle, error) {
|
||||
|
||||
back := &backend.Local{}
|
||||
sh, err := ps.New(back)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shell := sh
|
||||
|
||||
pssession := config["pssession"]
|
||||
if pssession != "" {
|
||||
fmt.Printf("INFO: PowerShell commands will run on %q\n", pssession)
|
||||
// create a remote shell by wrapping the existing one in the session middleware
|
||||
mconfig := middleware.NewSessionConfig()
|
||||
mconfig.ComputerName = pssession
|
||||
|
||||
session, err := middleware.NewSession(sh, mconfig)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
shell = session
|
||||
}
|
||||
|
||||
psh := &psHandle{
|
||||
shell: shell,
|
||||
}
|
||||
return psh, nil
|
||||
}
|
||||
|
||||
func (psh *psHandle) Exit() {
|
||||
psh.shell.Exit()
|
||||
}
|
||||
|
||||
type dnsZone map[string]interface{}
|
||||
|
||||
func (psh *psHandle) GetDNSServerZoneAll(dnsserver string) ([]string, error) {
|
||||
stdout, stderr, err := psh.shell.Execute(generatePSZoneAll(dnsserver))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stderr != "" {
|
||||
fmt.Printf("STDERROR = %q\n", stderr)
|
||||
return nil, fmt.Errorf("unexpected stderr from Get-DnsServerZones: %q", stderr)
|
||||
}
|
||||
|
||||
var zones []dnsZone
|
||||
json.Unmarshal([]byte(stdout), &zones)
|
||||
|
||||
var result []string
|
||||
for _, z := range zones {
|
||||
zonename := z["ZoneName"].(string)
|
||||
result = append(result, zonename)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// powerShellDump runs a PowerShell command to get a dump of all records in a DNS zone.
|
||||
func generatePSZoneAll(dnsserver string) string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, `Get-DnsServerZone`)
|
||||
if dnsserver != "" {
|
||||
fmt.Fprintf(&b, ` -ComputerName "%v"`, dnsserver)
|
||||
}
|
||||
fmt.Fprintf(&b, ` | `)
|
||||
fmt.Fprintf(&b, `ConvertTo-Json`)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (psh *psHandle) GetDNSZoneRecords(dnsserver, domain string) ([]nativeRecord, error) {
|
||||
tmpfile, err := ioutil.TempFile("", "zonerecords.*.json")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
tmpfile.Close()
|
||||
|
||||
stdout, stderr, err := psh.shell.Execute(generatePSZoneDump(dnsserver, domain, tmpfile.Name()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stdout != "" {
|
||||
fmt.Printf("STDOUT = %q\n", stderr)
|
||||
}
|
||||
if stderr != "" {
|
||||
fmt.Printf("STDERROR = %q\n", stderr)
|
||||
return nil, fmt.Errorf("unexpected stderr from PSZoneDump: %q", stderr)
|
||||
}
|
||||
|
||||
contents, err := utfutil.ReadFile(tmpfile.Name(), utfutil.WINDOWS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
os.Remove(tmpfile.Name())
|
||||
|
||||
var records []nativeRecord
|
||||
json.Unmarshal([]byte(contents), &records)
|
||||
|
||||
return records, nil
|
||||
}
|
||||
|
||||
// powerShellDump runs a PowerShell command to get a dump of all records in a DNS zone.
|
||||
func generatePSZoneDump(dnsserver, domainname string, filename string) string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, `Get-DnsServerResourceRecord`)
|
||||
if dnsserver != "" {
|
||||
fmt.Fprintf(&b, ` -ComputerName "%v"`, dnsserver)
|
||||
}
|
||||
fmt.Fprintf(&b, ` -ZoneName "%v"`, domainname)
|
||||
fmt.Fprintf(&b, ` | `)
|
||||
fmt.Fprintf(&b, `ConvertTo-Json -depth 4`) // Tested with 3 (causes errors). 4 and larger work.
|
||||
fmt.Fprintf(&b, ` > %s`, filename)
|
||||
//fmt.Printf("DEBUG PSZoneDump CMD = (\n%s\n)\n", b.String())
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// Functions for record manipulation
|
||||
|
||||
func (psh *psHandle) RecordDelete(dnsserver, domain string, rec *models.RecordConfig) error {
|
||||
_, stderr, err := psh.shell.Execute(generatePSDelete(dnsserver, domain, rec))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stderr != "" {
|
||||
fmt.Printf("STDERROR = %q\n", stderr)
|
||||
return fmt.Errorf("unexpected stderr from PSDelete: %q", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePSDelete(dnsserver, domain string, rec *models.RecordConfig) string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, `echo DELETE "%s" "%s" "%s"`, rec.Type, rec.Name, rec.GetTargetCombined())
|
||||
fmt.Fprintf(&b, " ; ")
|
||||
fmt.Fprintf(&b, `Remove-DnsServerResourceRecord`)
|
||||
if dnsserver != "" {
|
||||
fmt.Fprintf(&b, ` -ComputerName "%s"`, dnsserver)
|
||||
}
|
||||
fmt.Fprintf(&b, ` -Force`)
|
||||
fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
|
||||
fmt.Fprintf(&b, ` -Name "%s"`, rec.Name)
|
||||
fmt.Fprintf(&b, ` -RRType "%s"`, rec.Type)
|
||||
if rec.Type == "MX" {
|
||||
fmt.Fprintf(&b, ` -RecordData %d,"%s"`, rec.MxPreference, rec.GetTargetField())
|
||||
} else if rec.Type == "SRV" {
|
||||
// https://www.gitmemory.com/issue/MicrosoftDocs/windows-powershell-docs/1149/511916884
|
||||
fmt.Fprintf(&b, ` -RecordData %d,%d,%d,"%s"`, rec.SrvPriority, rec.SrvWeight, rec.SrvPort, rec.GetTargetField())
|
||||
} else {
|
||||
fmt.Fprintf(&b, ` -RecordData "%s"`, rec.GetTargetField())
|
||||
}
|
||||
//fmt.Printf("DEBUG PSDelete CMD = (\n%s\n)\n", b.String())
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (psh *psHandle) RecordCreate(dnsserver, domain string, rec *models.RecordConfig) error {
|
||||
_, stderr, err := psh.shell.Execute(generatePSCreate(dnsserver, domain, rec))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stderr != "" {
|
||||
fmt.Printf("STDERROR = %q\n", stderr)
|
||||
return fmt.Errorf("unexpected stderr from PSCreate: %q", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePSCreate(dnsserver, domain string, rec *models.RecordConfig) string {
|
||||
var b bytes.Buffer
|
||||
fmt.Fprintf(&b, `echo CREATE "%s" "%s" "%s"`, rec.Type, rec.Name, rec.GetTargetCombined())
|
||||
fmt.Fprintf(&b, " ; ")
|
||||
|
||||
fmt.Fprint(&b, `Add-DnsServerResourceRecord`)
|
||||
if dnsserver != "" {
|
||||
fmt.Fprintf(&b, ` -ComputerName "%s"`, dnsserver)
|
||||
}
|
||||
fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
|
||||
fmt.Fprintf(&b, ` -Name "%s"`, rec.GetLabel())
|
||||
fmt.Fprintf(&b, ` -TimeToLive $(New-TimeSpan -Seconds %d)`, rec.TTL)
|
||||
switch rec.Type {
|
||||
case "A":
|
||||
fmt.Fprintf(&b, ` -A -IPv4Address "%s"`, rec.GetTargetIP())
|
||||
case "AAAA":
|
||||
fmt.Fprintf(&b, ` -AAAA -IPv6Address "%s"`, rec.GetTargetIP())
|
||||
//case "ATMA":
|
||||
// fmt.Fprintf(&b, ` -Atma -Address <String> -AddressType {E164 | AESA}`, rec.GetTargetField())
|
||||
//case "AFSDB":
|
||||
// fmt.Fprintf(&b, ` -Afsdb -ServerName <String> -SubType <UInt16>`, rec.GetTargetField())
|
||||
case "SRV":
|
||||
fmt.Fprintf(&b, ` -Srv -DomainName "%s" -Port %d -Priority %d -Weight %d`, rec.GetTargetField(), rec.SrvPort, rec.SrvPriority, rec.SrvWeight)
|
||||
case "CNAME":
|
||||
fmt.Fprintf(&b, ` -CName -HostNameAlias "%s"`, rec.GetTargetField())
|
||||
//case "X25":
|
||||
// fmt.Fprintf(&b, ` -X25 -PsdnAddress <String>`, rec.GetTargetField())
|
||||
//case "WKS":
|
||||
// fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
|
||||
case "TXT":
|
||||
fmt.Fprintf(&b, ` -Txt -DescriptiveText "%s"`, rec.GetTargetField())
|
||||
//case "RT":
|
||||
// fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, rec.GetTargetField())
|
||||
//case "RP":
|
||||
// fmt.Fprintf(&b, ` -RP -Description <String> -ResponsiblePerson <String>`, rec.GetTargetField())
|
||||
case "PTR":
|
||||
fmt.Fprintf(&b, ` -Ptr -PtrDomainName "%s"`, rec.GetTargetField())
|
||||
case "NS":
|
||||
fmt.Fprintf(&b, ` -NS -NameServer "%s"`, rec.GetTargetField())
|
||||
case "MX":
|
||||
fmt.Fprintf(&b, ` -MX -MailExchange "%s" -Preference %d`, rec.GetTargetField(), rec.MxPreference)
|
||||
//case "ISDN":
|
||||
// fmt.Fprintf(&b, ` -Isdn -IsdnNumber <String> -IsdnSubAddress <String>`, rec.GetTargetField())
|
||||
//case "HINFO":
|
||||
// fmt.Fprintf(&b, ` -HInfo -Cpu <String> -OperatingSystem <String>`, rec.GetTargetField())
|
||||
//case "DNAME":
|
||||
// fmt.Fprintf(&b, ` -DName -DomainNameAlias <String>`, rec.GetTargetField())
|
||||
//case "DHCID":
|
||||
// fmt.Fprintf(&b, ` -DhcId -DhcpIdentifier <String>`, rec.GetTargetField())
|
||||
//case "TLSA":
|
||||
// fmt.Fprintf(&b, ` -TLSA -CertificateAssociationData <System.String> -CertificateUsage {CAConstraint | ServiceCertificateConstraint | TrustAnchorAssertion | DomainIssuedCertificate} -MatchingType {ExactMatch | Sha256Hash | Sha512Hash} -Selector {FullCertificate | SubjectPublicKeyInfo}`, rec.GetTargetField())
|
||||
default:
|
||||
panic(fmt.Errorf("generatePSCreate() has not implemented recType=%s recName=%#v content=%#v)",
|
||||
rec.Type, rec.GetLabel(), rec.GetTargetField()))
|
||||
// We panic so that we quickly find any switch statements
|
||||
// that have not been updated for a new RR type.
|
||||
}
|
||||
//fmt.Printf("DEBUG PSCreate CMD = (\n%s\n)\n", b.String())
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (psh *psHandle) RecordModify(dnsserver, domain string, old, rec *models.RecordConfig) error {
|
||||
_, stderr, err := psh.shell.Execute(generatePSModify(dnsserver, domain, old, rec))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if stderr != "" {
|
||||
fmt.Printf("STDERROR = %q\n", stderr)
|
||||
return fmt.Errorf("unexpected stderr from PSModify: %q", stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func generatePSModify(dnsserver, domain string, old, rec *models.RecordConfig) string {
|
||||
// The simple way is to just remove the old record and insert the new record.
|
||||
return generatePSDelete(dnsserver, domain, old) + ` ; ` + generatePSCreate(dnsserver, domain, rec)
|
||||
// NB: SOA records can't be deleted. When we implement them, we'll
|
||||
// need to special case them and generate an in-place modification
|
||||
// command.
|
||||
}
|
||||
|
||||
// Note about the old generatePSModify:
|
||||
//
|
||||
// The old method is to generate PowerShell code that extracts the resource
|
||||
// record, clones it, makes modifications to the clone, and replaces the old
|
||||
// object with the modified clone. In theory this is cleaner.
|
||||
//
|
||||
// Sadly that technique is considerably slower (PowerShell seems to take a
|
||||
// long time doing it) and it is more brittle (each new rType seems to be a
|
||||
// new adventure).
|
||||
//
|
||||
// The other benefit of the Delete/Create method is that it more heavily
|
||||
// exercises existing code that is known to work.
|
||||
//
|
||||
// Sadly I can't bring myself to erase the code yet. I still hope this can
|
||||
// be fixed. Deep down I know we should just accept that Del/Create is better.
|
||||
|
||||
// if old.GetLabel() != rec.GetLabel() {
|
||||
// panic(fmt.Sprintf("generatePSModify assertion failed: %q != %q", old.GetLabel(), rec.GetLabel()))
|
||||
// }
|
||||
//
|
||||
// var b bytes.Buffer
|
||||
// fmt.Fprintf(&b, `echo "MODIFY %s %s %s old=(%s) new=(%s):"`, rec.GetLabel(), domain, rec.Type, old.GetTargetCombined(), rec.GetTargetCombined())
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, "$OldObj = Get-DnsServerResourceRecord")
|
||||
// fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
|
||||
// fmt.Fprintf(&b, ` -Name "%s"`, old.GetLabel())
|
||||
// fmt.Fprintf(&b, ` -RRType "%s"`, old.Type)
|
||||
// fmt.Fprintf(&b, ` | Where-Object {$_.HostName eq "%s" -and -RRType -eq "%s" -and `, old.GetLabel(), rec.Type)
|
||||
// switch old.Type {
|
||||
// case "A":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.IPv4Address -eq "%s"`, old.GetTargetIP())
|
||||
// case "AAAA":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.IPv6Address -eq "%s"`, old.GetTargetIP())
|
||||
// //case "ATMA":
|
||||
// // fmt.Fprintf(&b, ` -Atma -Address <String> -AddressType {E164 | AESA}`, old.GetTargetField())
|
||||
// //case "AFSDB":
|
||||
// // fmt.Fprintf(&b, ` -Afsdb -ServerName <String> -SubType <UInt16>`, old.GetTargetField())
|
||||
// case "SRV":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.DomainName -eq "%s" -and $_.RecordData.Port -eq %d -and $_.RecordData.Priority -eq %d -and $_.RecordData.Weight -eq %d`, old.GetTargetField(), old.SrvPort, old.SrvPriority, old.SrvWeight)
|
||||
// case "CNAME":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.HostNameAlias -eq "%s"`, old.GetTargetField())
|
||||
// //case "X25":
|
||||
// // fmt.Fprintf(&b, ` -X25 -PsdnAddress <String>`, old.GetTargetField())
|
||||
// //case "WKS":
|
||||
// // fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, old.GetTargetField())
|
||||
// case "TXT":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.DescriptiveText -eq "%s"`, old.GetTargetField())
|
||||
// //case "RT":
|
||||
// // fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, old.GetTargetField())
|
||||
// //case "RP":
|
||||
// // fmt.Fprintf(&b, ` -RP -Description <String> -ResponsiblePerson <String>`, old.GetTargetField())
|
||||
// case "PTR":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.PtrDomainName -eq "%s"`, old.GetTargetField())
|
||||
// case "NS":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.NameServer -eq "%s"`, old.GetTargetField())
|
||||
// case "MX":
|
||||
// fmt.Fprintf(&b, `$_.RecordData.MailExchange -eq "%s" -and $_.RecordData.Preference -eq %d`, old.GetTargetField(), old.MxPreference)
|
||||
// //case "ISDN":
|
||||
// // fmt.Fprintf(&b, ` -Isdn -IsdnNumber <String> -IsdnSubAddress <String>`, old.GetTargetField())
|
||||
// //case "HINFO":
|
||||
// // fmt.Fprintf(&b, ` -HInfo -Cpu <String> -OperatingSystem <String>`, old.GetTargetField())
|
||||
// //case "DNAME":
|
||||
// // fmt.Fprintf(&b, ` -DName -DomainNameAlias <String>`, old.GetTargetField())
|
||||
// //case "DHCID":
|
||||
// // fmt.Fprintf(&b, ` -DhcId -DhcpIdentifier <String>`, old.GetTargetField())
|
||||
// //case "TLSA":
|
||||
// // fmt.Fprintf(&b, ` -TLSA -CertificateAssociationData <System.String> -CertificateUsage {CAConstraint | ServiceCertificateConstraint | TrustAnchorAssertion | DomainIssuedCertificate} -MatchingType {ExactMatch | Sha256Hash | Sha512Hash} -Selector {FullCertificate | SubjectPublicKeyInfo}`, rec.GetTargetField())
|
||||
// default:
|
||||
// panic(fmt.Errorf("generatePSModify() has not implemented recType=%q recName=%q content=(%s))",
|
||||
// rec.Type, rec.GetLabel(), rec.GetTargetCombined()))
|
||||
// // We panic so that we quickly find any switch statements
|
||||
// // that have not been updated for a new RR type.
|
||||
// }
|
||||
// fmt.Fprintf(&b, "}")
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, `if($OldObj.Length -ne 1){ throw "Error, multiple results for Get-DnsServerResourceRecord" }`)
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, "$NewObj = $OldObj.Clone()")
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
//
|
||||
// if old.TTL != rec.TTL {
|
||||
// fmt.Fprintf(&b, `$NewObj.TimeToLive = New-TimeSpan -Seconds %d`, rec.TTL)
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// }
|
||||
// switch rec.Type {
|
||||
// case "A":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.IPv4Address = "%s"`, rec.GetTargetIP())
|
||||
// case "AAAA":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.IPv6Address = "%s"`, rec.GetTargetIP())
|
||||
// //case "ATMA":
|
||||
// // fmt.Fprintf(&b, ` -Atma -Address <String> -AddressType {E164 | AESA}`, rec.GetTargetField())
|
||||
// //case "AFSDB":
|
||||
// // fmt.Fprintf(&b, ` -Afsdb -ServerName <String> -SubType <UInt16>`, rec.GetTargetField())
|
||||
// case "SRV":
|
||||
// fmt.Fprintf(&b, ` -Srv -DomainName "%s" -Port %d -Priority %d -Weight %d`, rec.GetTargetField(), rec.SrvPort, rec.SrvPriority, rec.SrvWeight)
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.DomainName = "%s"`, rec.GetTargetField())
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.Port = %d`, rec.SrvPort)
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.Priority = %d`, rec.SrvPriority)
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.Weight = "%d"`, rec.SrvWeight)
|
||||
// case "CNAME":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.HostNameAlias = "%s"`, rec.GetTargetField())
|
||||
// //case "X25":
|
||||
// // fmt.Fprintf(&b, ` -X25 -PsdnAddress <String>`, rec.GetTargetField())
|
||||
// //case "WKS":
|
||||
// // fmt.Fprintf(&b, ` -Wks -InternetAddress <IPAddress> -InternetProtocol {UDP | TCP} -Service <String[]>`, rec.GetTargetField())
|
||||
// case "TXT":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.DescriptiveText = "%s"`, rec.GetTargetField())
|
||||
// //case "RT":
|
||||
// // fmt.Fprintf(&b, ` -RT -IntermediateHost <String> -Preference <UInt16>`, rec.GetTargetField())
|
||||
// //case "RP":
|
||||
// // fmt.Fprintf(&b, ` -RP -Description <String> -ResponsiblePerson <String>`, rec.GetTargetField())
|
||||
// case "PTR":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.PtrDomainName = "%s"`, rec.GetTargetField())
|
||||
// case "NS":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.NameServer = "%s"`, rec.GetTargetField())
|
||||
// case "MX":
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.MailExchange = "%s"`, rec.GetTargetField())
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// fmt.Fprintf(&b, `$NewObj.RecordData.Preference = "%d"`, rec.MxPreference)
|
||||
// //case "ISDN":
|
||||
// // fmt.Fprintf(&b, ` -Isdn -IsdnNumber <String> -IsdnSubAddress <String>`, rec.GetTargetField())
|
||||
// //case "HINFO":
|
||||
// // fmt.Fprintf(&b, ` -HInfo -Cpu <String> -OperatingSystem <String>`, rec.GetTargetField())
|
||||
// //case "DNAME":
|
||||
// // fmt.Fprintf(&b, ` -DName -DomainNameAlias <String>`, rec.GetTargetField())
|
||||
// //case "DHCID":
|
||||
// // fmt.Fprintf(&b, ` -DhcId -DhcpIdentifier <String>`, rec.GetTargetField())
|
||||
// //case "TLSA":
|
||||
// // fmt.Fprintf(&b, ` -TLSA -CertificateAssociationData <System.String> -CertificateUsage {CAConstraint | ServiceCertificateConstraint | TrustAnchorAssertion | DomainIssuedCertificate} -MatchingType {ExactMatch | Sha256Hash | Sha512Hash} -Selector {FullCertificate | SubjectPublicKeyInfo}`, rec.GetTargetField())
|
||||
// default:
|
||||
// panic(fmt.Errorf("generatePSModify() update has not implemented recType=%q recName=%q content=(%s))",
|
||||
// rec.Type, rec.GetLabel(), rec.GetTargetCombined()))
|
||||
// // We panic so that we quickly find any switch statements
|
||||
// // that have not been updated for a new RR type.
|
||||
// }
|
||||
// fmt.Fprintf(&b, " ; ")
|
||||
// //fmt.Printf("DEBUG CCMD: %s\n", b.String())
|
||||
//
|
||||
// fmt.Fprintf(&b, "Set-DnsServerResourceRecord")
|
||||
// fmt.Fprintf(&b, ` -ZoneName "%s"`, domain)
|
||||
// fmt.Fprintf(&b, ` -NewInputObject $NewObj -OldInputObject $OldObj`)
|
||||
//
|
||||
// fmt.Printf("DEBUG MCMD: %s", b.String())
|
||||
// return b.String()
|
169
providers/msdns/powershell_test.go
Normal file
169
providers/msdns/powershell_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package msdns
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
)
|
||||
|
||||
func Test_generatePSZoneAll(t *testing.T) {
|
||||
type args struct {
|
||||
dnsserver string
|
||||
domain string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "local",
|
||||
args: args{},
|
||||
want: `Get-DnsServerZone | ConvertTo-Json`,
|
||||
},
|
||||
{
|
||||
name: "remote",
|
||||
args: args{dnsserver: "mydnsserver"},
|
||||
want: `Get-DnsServerZone -ComputerName "mydnsserver" | ConvertTo-Json`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := generatePSZoneAll(tt.args.dnsserver); got != tt.want {
|
||||
t.Errorf("generatePSZoneAll() = got=(\n%s\n) want=(\n%s\n)", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_generatePSZoneDump(t *testing.T) {
|
||||
type args struct {
|
||||
domainname string
|
||||
dnsserver string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "local",
|
||||
args: args{domainname: "example.com"},
|
||||
want: `Get-DnsServerResourceRecord -ZoneName "example.com" | ConvertTo-Json -depth 4 > mytemp.json`,
|
||||
},
|
||||
{
|
||||
name: "remote",
|
||||
args: args{domainname: "example.com", dnsserver: "mydnsserver"},
|
||||
want: `Get-DnsServerResourceRecord -ComputerName "mydnsserver" -ZoneName "example.com" | ConvertTo-Json -depth 4 > mytemp.json`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := generatePSZoneDump(tt.args.dnsserver, tt.args.domainname, "mytemp.json"); got != tt.want {
|
||||
t.Errorf("generatePSZoneDump() = got=(\n%s\n) want=(\n%s\n)", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
//func Test_generatePSDelete(t *testing.T) {
|
||||
// type args struct {
|
||||
// domain string
|
||||
// rec *models.RecordConfig
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// want string
|
||||
// }{
|
||||
// // TODO: Add test cases.
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// if got := generatePSDelete(tt.args.domain, tt.args.rec); got != tt.want {
|
||||
// t.Errorf("generatePSDelete() = %v, want %v", got, tt.want)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
//}
|
||||
|
||||
// func Test_generatePSCreate(t *testing.T) {
|
||||
// type args struct {
|
||||
// domain string
|
||||
// rec *models.RecordConfig
|
||||
// }
|
||||
// tests := []struct {
|
||||
// name string
|
||||
// args args
|
||||
// want string
|
||||
// }{
|
||||
// // TODO: Add test cases.
|
||||
// }
|
||||
// for _, tt := range tests {
|
||||
// t.Run(tt.name, func(t *testing.T) {
|
||||
// if got := generatePSCreate(tt.args.domain, tt.args.rec); got != tt.want {
|
||||
// t.Errorf("generatePSCreate() = %v, want %v", got, tt.want)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
func Test_generatePSModify(t *testing.T) {
|
||||
|
||||
recA1 := &models.RecordConfig{
|
||||
Type: "A",
|
||||
Name: "@",
|
||||
Target: "1.2.3.4",
|
||||
}
|
||||
recA2 := &models.RecordConfig{
|
||||
Type: "A",
|
||||
Name: "@",
|
||||
Target: "10.20.30.40",
|
||||
}
|
||||
|
||||
recMX1 := &models.RecordConfig{
|
||||
Type: "MX",
|
||||
Name: "@",
|
||||
Target: "foo.com.",
|
||||
MxPreference: 5,
|
||||
}
|
||||
recMX2 := &models.RecordConfig{
|
||||
Type: "MX",
|
||||
Name: "@",
|
||||
Target: "foo2.com.",
|
||||
MxPreference: 50,
|
||||
}
|
||||
|
||||
type args struct {
|
||||
domain string
|
||||
dnsserver string
|
||||
old *models.RecordConfig
|
||||
rec *models.RecordConfig
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want string
|
||||
}{
|
||||
{name: "A", args: args{domain: "example.com", dnsserver: "", old: recA1, rec: recA2},
|
||||
want: `echo DELETE "A" "@" "1.2.3.4" ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "10.20.30.40" ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`,
|
||||
},
|
||||
{name: "MX1", args: args{domain: "example.com", dnsserver: "", old: recMX1, rec: recMX2},
|
||||
want: `echo DELETE "MX" "@" "5 foo.com." ; Remove-DnsServerResourceRecord -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "50 foo2.com." ; Add-DnsServerResourceRecord -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`,
|
||||
},
|
||||
{name: "A-remote", args: args{domain: "example.com", dnsserver: "myremote", old: recA1, rec: recA2},
|
||||
want: `echo DELETE "A" "@" "1.2.3.4" ; Remove-DnsServerResourceRecord -ComputerName "myremote" -Force -ZoneName "example.com" -Name "@" -RRType "A" -RecordData "1.2.3.4" ; echo CREATE "A" "@" "10.20.30.40" ; Add-DnsServerResourceRecord -ComputerName "myremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -A -IPv4Address "10.20.30.40"`,
|
||||
},
|
||||
{name: "MX1-remote", args: args{domain: "example.com", dnsserver: "yourremote", old: recMX1, rec: recMX2},
|
||||
want: `echo DELETE "MX" "@" "5 foo.com." ; Remove-DnsServerResourceRecord -ComputerName "yourremote" -Force -ZoneName "example.com" -Name "@" -RRType "MX" -RecordData 5,"foo.com." ; echo CREATE "MX" "@" "50 foo2.com." ; Add-DnsServerResourceRecord -ComputerName "yourremote" -ZoneName "example.com" -Name "@" -TimeToLive $(New-TimeSpan -Seconds 0) -MX -MailExchange "foo2.com." -Preference 50`,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := generatePSModify(tt.args.dnsserver, tt.args.domain, tt.args.old, tt.args.rec); strings.TrimSpace(got) != strings.TrimSpace(tt.want) {
|
||||
t.Errorf("generatePSModify() = got=(\n%s\n) want=(\n%s\n)", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
48
providers/msdns/types.go
Normal file
48
providers/msdns/types.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package msdns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/StackExchange/dnscontrol/v3/models"
|
||||
)
|
||||
|
||||
// DNSAccessor describes a system that can access Microsoft DNS.
|
||||
type DNSAccessor interface {
|
||||
Exit()
|
||||
GetDNSServerZoneAll(dnsserver string) ([]string, error)
|
||||
GetDNSZoneRecords(dnsserver, domain string) ([]nativeRecord, error)
|
||||
RecordCreate(dnsserver, domain string, rec *models.RecordConfig) error
|
||||
RecordDelete(dnsserver, domain string, rec *models.RecordConfig) error
|
||||
RecordModify(dnsserver, domain string, old, rec *models.RecordConfig) error
|
||||
}
|
||||
|
||||
// nativeRecord the JSON received from PowerShell when listing all DNS
|
||||
// records in a zone.
|
||||
type nativeRecord struct {
|
||||
//CimClass interface{} `json:"CimClass"`
|
||||
//CimInstanceProperties interface{} `json:"CimInstanceProperties"`
|
||||
//CimSystemProperties interface{} `json:"CimSystemProperties"`
|
||||
//DistinguishedName string `json:"DistinguishedName"`
|
||||
//RecordClass string `json:"RecordClass"`
|
||||
RecordType string `json:"RecordType"`
|
||||
HostName string `json:"HostName"`
|
||||
RecordData struct {
|
||||
CimInstanceProperties []ciProperty `json:"CimInstanceProperties"`
|
||||
} `json:"RecordData"`
|
||||
TimeToLive struct {
|
||||
TotalSeconds float64 `json:"TotalSeconds"`
|
||||
} `json:"TimeToLive"`
|
||||
}
|
||||
|
||||
type ciProperty struct {
|
||||
Name string `json:"Name"`
|
||||
Value json.RawMessage `json:"Value,omitempty"`
|
||||
}
|
||||
|
||||
type ciValueDuration struct {
|
||||
TotalSeconds float64 `json:"TotalSeconds"`
|
||||
}
|
||||
|
||||
// NB(tlim): The above structs were created using the help of:
|
||||
// Get-DnsServerResourceRecord -ZoneName example.com | where { $_.RecordType -eq "SOA" } | select $_.RecordData | ConvertTo-Json -depth 10
|
||||
// and pass it to https://mholt.github.io/json-to-go/
|
Reference in New Issue
Block a user