Compare commits

..

9 Commits

Author SHA1 Message Date
37ae1e217d Added simple error handling 2025-07-24 04:53:01 +03:00
bdb2539eba Stabilized driver API 2025-07-19 23:03:52 +03:00
4a9e8f758e Added README.md 2025-07-19 20:05:47 +03:00
659918983b Change .gitignore: added *.sum wildcard 2025-07-19 19:49:52 +03:00
62704fac46 Removed unnecessary function 2025-01-20 02:01:36 +03:00
6a1c07d3f5 Fixed the way it operates with dublicated DNS records 2025-01-20 01:59:41 +03:00
7270f4c91c Fixed range based for loop 2025-01-19 23:40:55 +03:00
19b44c14d9 Debugging the Set data struct. Fix mistyping 2025-01-19 23:19:56 +03:00
05e035925f Debugging the Set data struct. Fix mistyping 2025-01-19 23:05:46 +03:00
5 changed files with 151 additions and 107 deletions

17
README.md Normal file
View File

@ -0,0 +1,17 @@
### dnsexit-manager library
This is a part of my handmade DNS management project.
It's a driver that's create ability to manage your DNSexit subdomains via API.
#### How it works?
This driver relies on `dns-manager` library which describes all drivers API you may use.
The architecture is as simple as ABC: the drivers must implement all methods from `dns-manager`
library to be compatible with it.
It means anyone else can extend this project by creating own driver for their DNS providers.
#### To-Do list
- [ ] Better error handling
- [ ] Docs for the project
- [ ] Make an example of usage

View File

@ -3,30 +3,30 @@ package dnsexit_manager
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt" "errors"
"io" "io"
"net/http" "net/http"
"sync" "sync"
dns "git.uoc.run.place/OxFF/dns-manager" dns "git.jolbec.icu/OxFF/dns-manager"
) )
const API_BASE_URL = "https://api.dnsexit.com/dns/" const API_BASE_URL = "https://api.dnsexit.com/dns/"
type Config struct { type Config struct {
DOMAIN_NAME string DomainName string
API_KEY string API_Key string
API_URL string API_URL string
} }
type client struct { type client struct {
Locker sync.Mutex Locker sync.Mutex
API_KEY string API_Key string
API_URL string API_URL string
Domain string Domain string
} }
type dnsexitDTO struct { type dnsexitRequestDTO struct {
ApiKey string `json:"apikey"` ApiKey string `json:"apikey"`
Domain string `json:"domain"` Domain string `json:"domain"`
Add *dns.Record `json:"add,omitempty"` Add *dns.Record `json:"add,omitempty"`
@ -34,63 +34,74 @@ type dnsexitDTO struct {
Delete *dns.Record `json:"delete,omitempty"` Delete *dns.Record `json:"delete,omitempty"`
} }
func convertInterfaceSliceToStruct(slice []interface{}) *[]dns.Domain { type dnsexitResponseDTO struct {
var domains []dns.Domain Code int
Message string
for _, domain := range slice {
fmt.Println(domain)
}
return &domains
} }
func parseError(body *[]byte) error {
var response dnsexitResponseDTO
func (c *client) AddRecord(rec *dns.Record) (error, *dns.Response) { if err := json.Unmarshal(*body, &response); err != nil {
return err
}
if response.Code != 0 {
return errors.New(response.Message)
}
return nil
}
func (c *client) AddRecord(rec *dns.Record) (*dns.Response, error) {
c.Locker.Lock() c.Locker.Lock()
defer c.Locker.Unlock() defer c.Locker.Unlock()
request_body, err := json.Marshal(&dnsexitDTO{ request_body, err := json.Marshal(&dnsexitRequestDTO{
ApiKey: c.API_KEY, ApiKey: c.API_Key,
Domain: c.Domain, Domain: c.Domain,
Add: rec, Add: rec,
}) })
if err != nil { if err != nil {
return err, nil return nil, err
} }
fmt.Println(string(request_body)) response, err := http.Post(
resp, err := http.Post(
c.API_URL, c.API_URL,
"application/json", "application/json",
bytes.NewBuffer(request_body), bytes.NewBuffer(request_body),
) )
if err != nil { if err != nil {
return err, nil return nil, err
} }
defer resp.Body.Close() defer response.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
return err, nil return nil, err
} }
return nil, &dns.Response{Message: string(body)} if err = parseError(&body); err != nil {
return nil, err
}
return &dns.Response{Message: string(body)}, nil
} }
func (c *client) DeleteRecord(rec *dns.Record) (error, *dns.Response) { func (c *client) DeleteRecord(rec *dns.Record) (*dns.Response, error) {
c.Locker.Lock() c.Locker.Lock()
defer c.Locker.Unlock() defer c.Locker.Unlock()
request_body, err := json.Marshal(&dnsexitDTO{ request_body, err := json.Marshal(&dnsexitRequestDTO{
ApiKey: c.API_KEY, ApiKey: c.API_Key,
Domain: c.Domain, Domain: c.Domain,
Delete: rec, Delete: rec,
}) })
if err != nil { if err != nil {
return err, nil return nil, err
} }
resp, err := http.Post( resp, err := http.Post(
@ -98,30 +109,36 @@ func (c *client) DeleteRecord(rec *dns.Record) (error, *dns.Response) {
"application/json", "application/json",
bytes.NewBuffer(request_body), bytes.NewBuffer(request_body),
) )
if err != nil { if err != nil {
return err, nil return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err, nil return nil, err
} }
return nil, &dns.Response{Message: string(body)} if err = parseError(&body); err != nil {
return nil, err
}
return &dns.Response{Message: string(body)}, nil
} }
func (c *client) UpdateRecord(rec *dns.Record) (error, *dns.Response) { func (c *client) UpdateRecord(rec *dns.Record) (*dns.Response, error) {
c.Locker.Lock() c.Locker.Lock()
defer c.Locker.Unlock() defer c.Locker.Unlock()
request_body, err := json.Marshal(&dnsexitDTO{ request_body, err := json.Marshal(&dnsexitRequestDTO{
ApiKey: c.API_KEY, ApiKey: c.API_Key,
Domain: c.Domain, Domain: c.Domain,
Update: rec, Update: rec,
}) })
if err != nil { if err != nil {
return err, nil return nil, err
} }
resp, err := http.Post( resp, err := http.Post(
@ -129,37 +146,42 @@ func (c *client) UpdateRecord(rec *dns.Record) (error, *dns.Response) {
"application/json", "application/json",
bytes.NewBuffer(request_body), bytes.NewBuffer(request_body),
) )
if err != nil { if err != nil {
return err, nil return nil, err
} }
defer resp.Body.Close() defer resp.Body.Close()
body, err := io.ReadAll(resp.Body) body, err := io.ReadAll(resp.Body)
if err != nil { if err != nil {
return err, nil return nil, err
} }
return nil, &dns.Response{Message: string(body)} if err = parseError(&body); err != nil {
return nil, err
}
return &dns.Response{Message: string(body)}, nil
} }
func (c *client) GetRecords() (error, []*dns.Record) { func (c *client) GetRecords() (*[]dns.Record, error) {
var subdomains = CreateSet() var subdomains = NewRecordSet()
var wg sync.WaitGroup var wg sync.WaitGroup
result_chan := make(chan []*dns.Record) result_chan := make(chan []*dns.Record)
err_chan := make(chan error) err_chan := make(chan error)
err, namesevers := c.getNSRecods() nameservers, err := c.getNSRecods()
if err != nil { if err != nil {
return err, nil return nil, err
} }
for _, ns := range *namesevers { for _, ns := range *nameservers {
wg.Add(1) wg.Add(1)
go func(ns string) { go func(ns string) {
defer wg.Done() defer wg.Done()
err, records := find_dns_records(c.Domain, ns) records, err := find_dns_records(c.Domain, ns)
if err != nil { if err != nil {
err_chan <- err err_chan <- err
@ -180,20 +202,20 @@ func (c *client) GetRecords() (error, []*dns.Record) {
select { select {
case records, ok := <-result_chan: case records, ok := <-result_chan:
if !ok { if !ok {
return nil, convertInterfaceSliceToStruct(subdomains.List()) return subdomains.List(), nil
} }
for record := range records { for _, record := range records {
subdomains.Add(record) subdomains.Add(record)
} }
case _, _ = <-err_chan: case _, _ = <-err_chan:
} }
} }
} }
func (conf Config) New() dns.Actions { func (conf Config) New() (dns.Actions, error) {
var api_url string = API_BASE_URL var api_url = API_BASE_URL
if len(conf.API_URL) > 5 { if len(conf.API_URL) > 5 {
api_url = conf.API_URL api_url = conf.API_URL
@ -201,8 +223,8 @@ func (conf Config) New() dns.Actions {
return &client{ return &client{
Locker: sync.Mutex{}, Locker: sync.Mutex{},
API_KEY: conf.API_KEY, API_Key: conf.API_Key,
API_URL: api_url, API_URL: api_url,
Domain: conf.DOMAIN_NAME, Domain: conf.DomainName,
} }, nil
} }

21
go.mod
View File

@ -1,13 +1,16 @@
module git.uoc.run.place/OxFF/dnsexit-manager module git.jolbec.icu/OxFF/dnsexit-manager
go 1.23.4 go 1.23.4
require ( require (
git.uoc.run.place/OxFF/dns-manager v0.0.0-20250107205730-c4ab438a9fd5 // indirect git.jolbec.icu/OxFF/dns-manager v0.0.5
github.com/miekg/dns v1.1.62 // indirect github.com/miekg/dns v1.1.62
golang.org/x/mod v0.18.0 // indirect )
golang.org/x/net v0.27.0 // indirect
golang.org/x/sync v0.7.0 // indirect require (
golang.org/x/sys v0.22.0 // indirect golang.org/x/mod v0.22.0 // indirect
golang.org/x/tools v0.22.0 // indirect golang.org/x/net v0.34.0 // indirect
) golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/tools v0.29.0 // indirect
)

44
set.go
View File

@ -1,33 +1,35 @@
package dnsexit_manager package dnsexit_manager
import dns "git.jolbec.icu/OxFF/dns-manager"
type Set struct { type RecordSet struct {
elements map[interface{}]struct{} records map[dns.Record]struct{}
} }
func CreateSet() *Set { func NewRecordSet() *RecordSet {
return &Set{ return &RecordSet{
elements: make(map[interface{}]struct{}), records: make(map[dns.Record]struct{}),
} }
} }
func (set *Set) Add(value interface{}) { func (s *RecordSet) Add(record *dns.Record) {
_, found := set.elements[value] s.records[*record] = struct{}{}
if !found{
set.elements[value] = struct{}{}
}
} }
func (set *Set) Delete(value interface{}) { func (s *RecordSet) Remove(record dns.Record) {
delete(set.elements, value) delete(s.records, record)
} }
func (set *Set) List() []interface{} { func (s *RecordSet) Contains(record *dns.Record) bool {
keys := make([]interface{}, 0, len(set.elements)) _, exists := s.records[*record]
for key := range set.elements { return exists
keys = append(keys, key) }
}
func (s *RecordSet) List() *[]dns.Record {
return keys records := make([]dns.Record, 0, len(s.records))
for record := range s.records {
records = append(records, record)
}
return &records
} }

View File

@ -6,50 +6,50 @@ import (
"strconv" "strconv"
"strings" "strings"
dns "git.uoc.run.place/OxFF/dns-manager" dns "git.jolbec.icu/OxFF/dns-manager"
dns_req "github.com/miekg/dns" dns_req "github.com/miekg/dns"
) )
func (c *client) getNSRecods() (error, *[]string) { func (c *client) getNSRecods() (*[]string, error) {
var nameservers []string var nameservers []string
ns, err := net.LookupNS(c.Domain) ns, err := net.LookupNS(c.Domain)
if err != nil { if err != nil {
return err, nil return nil, err
} }
for _, v := range ns { for _, v := range ns {
nameservers = append(nameservers, v.Host[:len(v.Host)-1]+":53") nameservers = append(nameservers, v.Host[:len(v.Host)-1]+":53")
} }
return nil, &nameservers return &nameservers, nil
} }
func parse_record(record dns_req.RR) (error, *dns.Record) { func parse_record(record dns_req.RR) (*dns.Record, error) {
splitted := strings.SplitN(record.String(), "\t", -1) split := strings.SplitN(record.String(), "\t", -1)
if len(splitted) == 5 { if len(split) == 5 {
var parsed dns.Record var parsed dns.Record
rec_type := dns.RecordType(splitted[3]) rec_type := dns.RecordType(split[3])
if !rec_type.Check() { if !rec_type.Check() {
return errors.New("provided DNS type is not exist in the main library"), nil return nil, errors.New("provided DNS type is not exist in the main library")
} }
parsed.Type = rec_type parsed.Type = rec_type
ttl, _ := strconv.ParseUint(splitted[1], 10, 64) ttl, _ := strconv.ParseUint(split[1], 10, 64)
parsed.Name = splitted[0] parsed.Name = split[0]
parsed.Content = splitted[4] parsed.Content = split[4]
parsed.TTL = uint(ttl) parsed.TTL = uint(ttl)
return nil, &parsed return &parsed, nil
} }
return errors.New("no meaningful record provided"), nil return nil, errors.New("no meaningful record provided")
} }
func find_dns_records(domain string, nameserver string) (error, []*dns.Record) { func find_dns_records(domain string, nameserver string) ([]*dns.Record, error) {
var subdomains []*dns.Record var subdomains []*dns.Record
tr := new(dns_req.Transfer) tr := new(dns_req.Transfer)
@ -58,21 +58,21 @@ func find_dns_records(domain string, nameserver string) (error, []*dns.Record) {
client, err := tr.In(m, nameserver) client, err := tr.In(m, nameserver)
if err != nil { if err != nil {
return errors.New("failed to zone transfer in " + err.Error()), nil return nil, errors.New("failed to zone transfer in " + err.Error())
} }
for msg := range client { for msg := range client {
if msg.Error != nil { if msg.Error != nil {
return msg.Error, nil return nil, msg.Error
} }
for _, r := range msg.RR { for _, r := range msg.RR {
err, record := parse_record(r) record, err := parse_record(r)
if err == nil { if err == nil {
subdomains = append(subdomains, record) subdomains = append(subdomains, record)
} }
} }
} }
return nil, subdomains return subdomains, nil
} }