Initial commit.
This commit is contained in:
6
internal/storage/interface.go
Normal file
6
internal/storage/interface.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package storage
|
||||
|
||||
// Interface describes storage interface.
|
||||
type Interface interface {
|
||||
Process() error
|
||||
}
|
25
internal/storage/powerdns/client.go
Normal file
25
internal/storage/powerdns/client.go
Normal 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
|
||||
}
|
21
internal/storage/powerdns/data.go
Normal file
21
internal/storage/powerdns/data.go
Normal 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"`
|
||||
}
|
144
internal/storage/powerdns/powerdns.go
Normal file
144
internal/storage/powerdns/powerdns.go
Normal 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
|
||||
}
|
37
internal/storage/powerdns/request_get_zone_data.go
Normal file
37
internal/storage/powerdns/request_get_zone_data.go
Normal 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
|
||||
}
|
45
internal/storage/powerdns/request_update_zone_data.go
Normal file
45
internal/storage/powerdns/request_update_zone_data.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user