Implement Source abstraction
This commit is contained in:
parent
fa2ed5133f
commit
679bd9b9fa
2
main.go
2
main.go
|
@ -13,6 +13,8 @@ import (
|
|||
"git.happydns.org/happydns/config"
|
||||
"git.happydns.org/happydns/storage"
|
||||
leveldb "git.happydns.org/happydns/storage/leveldb"
|
||||
|
||||
_ "git.happydns.org/happydns/sources/ddns"
|
||||
)
|
||||
|
||||
type ResponseWriterPrefix struct {
|
||||
|
|
|
@ -5,9 +5,9 @@ import (
|
|||
)
|
||||
|
||||
type Domain struct {
|
||||
Id int64 `json:"id"`
|
||||
IdUser int64
|
||||
IdSource int64
|
||||
Id int64 `json:"id"`
|
||||
IdUser int64 `json:"id_owner"`
|
||||
IdSource int64 `json:"id_source"`
|
||||
DomainName string `json:"domain"`
|
||||
}
|
||||
|
||||
|
@ -21,10 +21,10 @@ func (d *Domain) NormalizedNSServer() string {
|
|||
}
|
||||
}
|
||||
|
||||
func NewDomain(u *User, s Source, dn string) (d *Domain) {
|
||||
func NewDomain(u *User, st *SourceType, dn string) (d *Domain) {
|
||||
d = &Domain{
|
||||
IdUser: u.Id,
|
||||
//IdSource: s.GetId(),
|
||||
IdUser: u.Id,
|
||||
IdSource: st.Id,
|
||||
DomainName: dn,
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
package happydns
|
||||
|
||||
import (
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
type Source interface {
|
||||
Validate() error
|
||||
ImportZone(*Domain) ([]dns.RR, error)
|
||||
AddRR(*Domain, dns.RR) error
|
||||
DeleteRR(*Domain, dns.RR) error
|
||||
}
|
||||
|
||||
type SourceType struct {
|
||||
Type string `json:"_srctype"`
|
||||
Id int64 `json:"_id"`
|
||||
OwnerId int64 `json:"_ownerid"`
|
||||
Comment string `json:"_comment,omitempty"`
|
||||
}
|
||||
|
||||
type SourceCombined struct {
|
||||
Source
|
||||
SourceType
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
package ddns // import "happydns.org/sources/ddns"
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
|
||||
"git.happydns.org/happydns/model"
|
||||
"git.happydns.org/happydns/sources"
|
||||
)
|
||||
|
||||
type DDNSServer struct {
|
||||
Server string `json:"server,omitempty"`
|
||||
KeyName string `json:"keyname,omitempty"`
|
||||
KeyAlgo string `json:"algorithm,omitempty"`
|
||||
KeyBlob []byte `json:"keyblob,omitempty"`
|
||||
}
|
||||
|
||||
func (s *DDNSServer) base64KeyBlob() string {
|
||||
return base64.StdEncoding.EncodeToString(s.KeyBlob)
|
||||
}
|
||||
|
||||
func (s *DDNSServer) Validate() error {
|
||||
d := net.Dialer{}
|
||||
con, err := d.Dial("tcp", s.Server)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer con.Close()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *DDNSServer) ImportZone(dn *happydns.Domain) (rrs []dns.RR, err error) {
|
||||
d := net.Dialer{}
|
||||
con, errr := d.Dial("tcp", s.Server)
|
||||
if errr != nil {
|
||||
err = errr
|
||||
return
|
||||
}
|
||||
defer con.Close()
|
||||
|
||||
m := new(dns.Msg)
|
||||
m.SetEdns0(4096, true)
|
||||
m.SetAxfr(dn.DomainName)
|
||||
m.SetTsig(s.KeyName, s.KeyAlgo, 300, time.Now().Unix())
|
||||
|
||||
dnscon := &dns.Conn{Conn: con}
|
||||
transfer := &dns.Transfer{Conn: dnscon, TsigSecret: map[string]string{s.KeyName: s.base64KeyBlob()}}
|
||||
c, errr := transfer.In(m, s.Server)
|
||||
|
||||
if errr != nil {
|
||||
err = errr
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
response, ok := <-c
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
for _, rr := range response.RR {
|
||||
rrs = append(rrs, rr)
|
||||
}
|
||||
}
|
||||
|
||||
if len(rrs) > 0 {
|
||||
rrs = rrs[0 : len(rrs)-1]
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *DDNSServer) AddRR(domain *happydns.Domain, rr dns.RR) error {
|
||||
m := new(dns.Msg)
|
||||
m.Id = dns.Id()
|
||||
m.Opcode = dns.OpcodeUpdate
|
||||
m.Question = make([]dns.Question, 1)
|
||||
m.Question[0] = dns.Question{domain.DomainName, dns.TypeSOA, dns.ClassINET}
|
||||
|
||||
m.Insert([]dns.RR{rr})
|
||||
|
||||
c := new(dns.Client)
|
||||
c.TsigSecret = map[string]string{s.KeyName: s.base64KeyBlob()}
|
||||
m.SetTsig(s.KeyName, s.KeyAlgo, 300, time.Now().Unix())
|
||||
|
||||
_, _, err := c.Exchange(m, s.Server)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *DDNSServer) DeleteRR(domain *happydns.Domain, rr dns.RR) error {
|
||||
m := new(dns.Msg)
|
||||
m.Id = dns.Id()
|
||||
m.Opcode = dns.OpcodeUpdate
|
||||
m.Question = make([]dns.Question, 1)
|
||||
m.Question[0] = dns.Question{domain.DomainName, dns.TypeSOA, dns.ClassINET}
|
||||
|
||||
m.Remove([]dns.RR{rr})
|
||||
|
||||
c := new(dns.Client)
|
||||
c.TsigSecret = map[string]string{s.KeyName: s.base64KeyBlob()}
|
||||
m.SetTsig(s.KeyName, s.KeyAlgo, 300, time.Now().Unix())
|
||||
|
||||
_, _, err := c.Exchange(m, s.Server)
|
||||
return err
|
||||
}
|
||||
|
||||
func init() {
|
||||
sources.RegisterSource("git.happydns.org/happydns/sources/ddns/DDNSServer", func() happydns.Source {
|
||||
return &DDNSServer{}
|
||||
})
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package sources // import "happydns.org/sources"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"git.happydns.org/happydns/model"
|
||||
)
|
||||
|
||||
type SourceCreator func() happydns.Source
|
||||
|
||||
var sources map[string]SourceCreator = map[string]SourceCreator{}
|
||||
|
||||
func RegisterSource(name string, creator SourceCreator) {
|
||||
log.Println("Registering new source:", name)
|
||||
sources[name] = creator
|
||||
}
|
||||
|
||||
func FindSource(name string) (happydns.Source, error) {
|
||||
src, ok := sources[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Unable to find corresponding source for `%s`.", name)
|
||||
}
|
||||
|
||||
return src(), nil
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package storage
|
||||
|
||||
import ()
|
||||
|
||||
type DBConfig interface{}
|
|
@ -24,6 +24,14 @@ type Storage interface {
|
|||
DeleteSession(session *happydns.Session) error
|
||||
ClearSessions() error
|
||||
|
||||
GetSourceTypes(u *happydns.User) ([]happydns.SourceType, error)
|
||||
GetSource(u *happydns.User, id int64) (*happydns.SourceCombined, error)
|
||||
CreateSource(u *happydns.User, s happydns.Source, comment string) (*happydns.SourceCombined, error)
|
||||
UpdateSource(s *happydns.SourceCombined) error
|
||||
UpdateSourceOwner(s *happydns.SourceCombined, newOwner *happydns.User) error
|
||||
DeleteSource(s *happydns.SourceType) error
|
||||
ClearSources() error
|
||||
|
||||
GetUsers() (happydns.Users, error)
|
||||
GetUser(id int) (*happydns.User, error)
|
||||
GetUserByEmail(email string) (*happydns.User, error)
|
||||
|
|
|
@ -57,7 +57,8 @@ func (s *LevelDBStorage) put(key string, v interface{}) error {
|
|||
func (s *LevelDBStorage) findInt63Key(prefix string) (key string, id int64, err error) {
|
||||
found := true
|
||||
for found {
|
||||
id = mrand.Int63()
|
||||
// max random id is 2^53 to fit on float64 without loosing precision (JSON limitation)
|
||||
id = mrand.Int63n(1 << 53)
|
||||
key = fmt.Sprintf("%s%d", prefix, id)
|
||||
|
||||
found, err = s.db.Has([]byte(key), nil)
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/syndtr/goleveldb/leveldb"
|
||||
"github.com/syndtr/goleveldb/leveldb/util"
|
||||
|
||||
"git.happydns.org/happydns/model"
|
||||
"git.happydns.org/happydns/sources"
|
||||
)
|
||||
|
||||
func (s *LevelDBStorage) GetSourceTypes(u *happydns.User) (srcs []happydns.SourceType, err error) {
|
||||
iter := s.search("source-")
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
var srcType happydns.SourceType
|
||||
err = decodeData(iter.Value(), &srcType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if srcType.OwnerId != u.Id {
|
||||
continue
|
||||
}
|
||||
|
||||
srcs = append(srcs, srcType)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) GetSource(u *happydns.User, id int64) (src *happydns.SourceCombined, err error) {
|
||||
var v []byte
|
||||
v, err = s.db.Get([]byte(fmt.Sprintf("source-%d", id)), nil)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var srcType happydns.SourceType
|
||||
err = decodeData(v, &srcType)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if srcType.OwnerId != u.Id {
|
||||
src = nil
|
||||
err = leveldb.ErrNotFound
|
||||
}
|
||||
|
||||
var tsrc happydns.Source
|
||||
tsrc, err = sources.FindSource(srcType.Type)
|
||||
|
||||
src = &happydns.SourceCombined{
|
||||
tsrc,
|
||||
srcType,
|
||||
}
|
||||
|
||||
err = decodeData(v, src)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) CreateSource(u *happydns.User, src happydns.Source, comment string) (*happydns.SourceCombined, error) {
|
||||
key, id, err := s.findInt63Key("source-")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sType := reflect.Indirect(reflect.ValueOf(src)).Type()
|
||||
|
||||
st := &happydns.SourceCombined{
|
||||
src,
|
||||
happydns.SourceType{
|
||||
Type: sType.PkgPath() + "/" + sType.Name(),
|
||||
Id: id,
|
||||
OwnerId: u.Id,
|
||||
Comment: comment,
|
||||
},
|
||||
}
|
||||
return st, s.put(key, st)
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) UpdateSource(src *happydns.SourceCombined) error {
|
||||
return s.put(fmt.Sprintf("source-%d", src.Id), src)
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) UpdateSourceOwner(src *happydns.SourceCombined, newOwner *happydns.User) error {
|
||||
src.OwnerId = newOwner.Id
|
||||
return s.UpdateSource(src)
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) DeleteSource(src *happydns.SourceType) error {
|
||||
return s.delete(fmt.Sprintf("source-%d", src.Id))
|
||||
}
|
||||
|
||||
func (s *LevelDBStorage) ClearSources() error {
|
||||
tx, err := s.db.OpenTransaction()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
iter := tx.NewIterator(util.BytesPrefix([]byte("source-")), nil)
|
||||
defer iter.Release()
|
||||
|
||||
for iter.Next() {
|
||||
err = tx.Delete(iter.Key(), nil)
|
||||
if err != nil {
|
||||
tx.Discard()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
tx.Discard()
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue