Initial commit.

This commit is contained in:
2021-06-06 23:13:55 +05:00
commit 462d09cee2
16 changed files with 631 additions and 0 deletions

View File

@@ -0,0 +1,6 @@
package storage
// Interface describes storage interface.
type Interface interface {
Process() error
}

View File

@@ -0,0 +1,25 @@
package powerdns
import (
"io"
"net/http"
"go.dev.pztrn.name/hosts-translator/internal/configuration"
)
// Executes request to PowerDNS server and returns data or error.
func (s *PowerDNS) request(req *http.Request) ([]byte, error) {
req.Header.Add("X-API-Key", configuration.PowerDNSAPIKey)
resp, err := s.httpClient.Do(req)
if err != nil {
return nil, err
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return data, nil
}

View File

@@ -0,0 +1,21 @@
package powerdns
type zoneData struct {
Name string `json:"name"`
RRSets []RRSet `json:"rrsets"`
Serial int64 `json:"serial"`
Type string `json:"type"`
}
type RRSet struct {
ChangeType string `json:"changetype"`
Name string `json:"name"`
Records []Record `json:"records"`
TTL int64 `json:"ttl"`
Type string `json:"type"`
}
type Record struct {
Content string `json:"content"`
Disabled bool `json:"disabled"`
}

View File

@@ -0,0 +1,144 @@
package powerdns
import (
"errors"
"log"
"net/http"
"strings"
"time"
"go.dev.pztrn.name/hosts-translator/internal/configuration"
"go.dev.pztrn.name/hosts-translator/internal/parser"
)
var ErrPowerDNSStorageError = errors.New("powerdns storage")
// PowerDNS is a controlling structure for PowerDNS storage.
type PowerDNS struct {
parser *parser.Parser
httpClient *http.Client
}
// NewPowerDNS creates new PowerDNS storage.
func NewPowerDNS(p *parser.Parser) *PowerDNS {
s := &PowerDNS{
parser: p,
}
s.initialize()
return s
}
// Initializes storage internal state.
func (s *PowerDNS) initialize() {
s.httpClient = &http.Client{
Timeout: time.Second * 5,
}
}
// Process processes parsed data.
func (s *PowerDNS) Process() error {
log.Println("Processing parsed data into PowerDNS storage...")
zoneData, err := s.getZoneData(configuration.DomainPostfix)
if err != nil {
return err
}
hostsParsedData := s.parser.GetParsedData()
// Check what we should to create, update or delete.
recordsToCreate := make([]RRSet, 0)
recordsToDelete := make([]RRSet, 0)
recordsToUpdate := make([]RRSet, 0)
// First iteration - figure out what to create or update.
for _, hostData := range hostsParsedData {
var found bool
for _, rrset := range zoneData.RRSets {
// We're only for A or AAAA things here.
if strings.ToUpper(rrset.Type) != "A" && strings.ToUpper(rrset.Type) != "AAAA" {
continue
}
// ToDo: multiple addresses support somehow?
if strings.TrimSuffix(rrset.Name, ".") == hostData.Domain {
found = true
if rrset.Records[0].Content != hostData.Address {
recordsToUpdate = append(recordsToUpdate, RRSet{
ChangeType: "REPLACE",
Name: rrset.Name,
Records: []Record{
{
Content: hostData.Address,
},
},
TTL: 300,
Type: rrset.Type,
})
}
break
}
}
if !found {
recordsToCreate = append(recordsToCreate, RRSet{
ChangeType: "REPLACE",
Name: hostData.Domain + ".",
Records: []Record{
{
Content: hostData.Address,
},
},
TTL: 300,
// ToDo: support for AAAA?
Type: "A",
})
}
}
// Second iteration - figure out what to delete.
for _, rrset := range zoneData.RRSets {
// We're only for A or AAAA things here.
if strings.ToUpper(rrset.Type) != "A" || strings.ToUpper(rrset.Type) != "AAAA" {
continue
}
var found bool
for _, hostData := range hostsParsedData {
if strings.TrimSuffix(rrset.Name, ".") == hostData.Domain+"." {
found = true
break
}
}
if !found {
rrset.ChangeType = "DELETE"
recordsToDelete = append(recordsToDelete, rrset)
}
}
log.Println("Got", len(zoneData.RRSets), "RRSets in NS")
log.Println("Got", len(recordsToCreate), "RRSets to create")
log.Println("Got", len(recordsToUpdate), "RRSets to update")
log.Println("Got", len(recordsToDelete), "RRSets to delete")
recordsUnchanged := len(zoneData.RRSets) - len(recordsToDelete) - len(recordsToUpdate)
log.Println("Got", recordsUnchanged, "RRSets unchanged")
// ToDo: '-debug'?
log.Printf("Got RRSets to create: %+v\n", recordsToCreate)
// log.Printf("Got RRSets to update: %+v\n", recordsToUpdate)
// log.Printf("Got RRSets to delete: %+v\n", recordsToDelete)
s.updateZoneData(zoneData.Name, append(recordsToCreate, append(recordsToUpdate, recordsToDelete...)...))
return nil
}

View File

@@ -0,0 +1,37 @@
package powerdns
import (
"encoding/json"
"log"
"net/http"
"strings"
"go.dev.pztrn.name/hosts-translator/internal/configuration"
)
// Gets zone data from PowerDNS.
func (s *PowerDNS) getZoneData(zoneName string) (*zoneData, error) {
log.Println("Getting zone data for domain", zoneName)
url := strings.Join([]string{configuration.PowerDNSURI, "api", "v1", "servers", "localhost", "zones", zoneName}, "/")
log.Println("URL:", url)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
bytesData, err := s.request(req)
if err != nil {
return nil, err
}
zd := &zoneData{}
if err := json.Unmarshal(bytesData, zd); err != nil {
return nil, err
}
return zd, nil
}

View File

@@ -0,0 +1,45 @@
package powerdns
import (
"bytes"
"encoding/json"
"log"
"net/http"
"strings"
"go.dev.pztrn.name/hosts-translator/internal/configuration"
)
// Updates zone data from PowerDNS.
func (s *PowerDNS) updateZoneData(zoneName string, RRSets []RRSet) error {
log.Println("Updating zone data for domain", zoneName)
zd := &zoneData{
Name: zoneName,
RRSets: RRSets,
Type: "Zone",
}
url := strings.Join([]string{configuration.PowerDNSURI, "api", "v1", "servers", "localhost", "zones", strings.TrimSuffix(zd.Name, ".")}, "/")
log.Println("URL:", url)
zoneBytes, err := json.Marshal(zd)
if err != nil {
return err
}
req, err := http.NewRequest("PATCH", url, bytes.NewReader(zoneBytes))
if err != nil {
return err
}
bytesData, err := s.request(req)
if err != nil {
return err
}
log.Println("Got response:", string(bytesData))
return nil
}