Skip to content

Commit

Permalink
Merge pull request #21 from nanopack/feature/postgres
Browse files Browse the repository at this point in the history
Add postgresql as a backend
  • Loading branch information
glinton committed Aug 18, 2016
2 parents 1b059f8 + 7f770f4 commit 01c36c0
Show file tree
Hide file tree
Showing 17 changed files with 374 additions and 83 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ Fields:
## Todo
- atomic local cache updates
- export in hosts file format
- improve scribble add (adding before stored in cache overwrites)


## Changelog
Expand All @@ -210,6 +211,8 @@ Fields:
- v0.0.3 (May 12, 2016)
- Tests for DNS server
- Start Server Insecure
- v0.0.4 (Aug 16, 2016)
- Postgresql as a backend


## Contributing
Expand Down
6 changes: 3 additions & 3 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ Small, lightweight, api-driven dns server.
| **POST** /records | Adds the domain and full record | json domain object | json domain object |
| **PUT** /records | Update all domains and records (replaces all) | json array of domain objects | json array of domain objects |
| **GET** /records | Returns a list of domains we have records for | nil | string array of domains |
| **PUT** /records/{id} | Update domain's records (replaces all) | json domain object | json domain object |
| **GET** /records/{id} | Returns the records for that domain | nil | json domain object |
| **DELETE** /records/{id} | Delete a domain | nil | success message |
| **PUT** /records/{domain} | Update domain's records (replaces all) | json domain object | json domain object |
| **GET** /records/{domain} | Returns the records for that domain | nil | json domain object |
| **DELETE** /records/{domain} | Delete a domain | nil | success message |

## Usage Example:

Expand Down
2 changes: 1 addition & 1 deletion api/records.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func updateRecord(rw http.ResponseWriter, req *http.Request) {
return
}

// "MUST reply 201"(https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)
// "MUST reply 201" (https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html)
writeBody(rw, req, resource, http.StatusCreated)
return
}
Expand Down
51 changes: 0 additions & 51 deletions build.sh

This file was deleted.

16 changes: 10 additions & 6 deletions cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ var (
// The cacher interface is what all the backends [will] implement
type cacher interface {
initialize() error
addRecord(resource *shaman.Resource) error
addRecord(resource shaman.Resource) error
getRecord(domain string) (*shaman.Resource, error)
updateRecord(domain string, resource *shaman.Resource) error
updateRecord(domain string, resource shaman.Resource) error
deleteRecord(domain string) error
resetRecords(resources *[]shaman.Resource) error
resetRecords(resources []shaman.Resource) error
listRecords() ([]shaman.Resource, error)
}

Expand All @@ -36,6 +36,10 @@ func Initialize() error {
switch u.Scheme {
case "scribble":
storage = &scribbleDb{}
case "postgres":
storage = &postgresDb{}
case "postgresql":
storage = &postgresDb{}
case "none":
storage = nil
default:
Expand All @@ -60,7 +64,7 @@ func AddRecord(resource *shaman.Resource) error {
return nil
}
resource.Validate()
return storage.addRecord(resource)
return storage.addRecord(*resource)
}

// GetRecord gets a record to the persistent cache
Expand All @@ -80,7 +84,7 @@ func UpdateRecord(domain string, resource *shaman.Resource) error {
}
shaman.SanitizeDomain(&domain)
resource.Validate()
return storage.updateRecord(domain, resource)
return storage.updateRecord(domain, *resource)
}

// DeleteRecord removes a record from the persistent cache
Expand All @@ -101,7 +105,7 @@ func ResetRecords(resources *[]shaman.Resource) error {
(*resources)[i].Validate()
}

return storage.resetRecords(resources)
return storage.resetRecords(*resources)
}

// ListRecords lists all records in the persistent cache
Expand Down
1 change: 1 addition & 0 deletions cache/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var (

func TestMain(m *testing.M) {
// manually configure
// config.Log = lumber.NewConsoleLogger(lumber.LvlInt("trace"))
config.Log = lumber.NewConsoleLogger(lumber.LvlInt("FATAL"))

// run tests
Expand Down
204 changes: 204 additions & 0 deletions cache/postgres.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package cache

import (
"database/sql"
"fmt"

_ "github.com/lib/pq"

"github.com/nanopack/shaman/config"
shaman "github.com/nanopack/shaman/core/common"
)

type postgresDb struct {
pg *sql.DB
}

func (p *postgresDb) connect() error {
// todo: example: config.DatabaseConnection = "postgres://postgres@127.0.0.1?sslmode=disable"
db, err := sql.Open("postgres", config.L2Connect)
if err != nil {
return fmt.Errorf("Failed to connect to postgres - %v", err)
}
err = db.Ping()
if err != nil {
return fmt.Errorf("Failed to ping postgres on connect - %v", err)
}

p.pg = db
return nil
}

func (p postgresDb) createTables() error {
// create records table
_, err := p.pg.Exec(`
CREATE TABLE IF NOT EXISTS records (
recordId SERIAL PRIMARY KEY NOT NULL,
domain TEXT NOT NULL,
address TEXT NOT NULL,
ttl INTEGER,
class TEXT,
type TEXT
)`)
if err != nil {
return fmt.Errorf("Failed to create records table - %v", err)
}

return nil
}

func (p *postgresDb) initialize() error {
err := p.connect()
if err != nil {
return fmt.Errorf("Failed to create new connection - %v", err)
}

// create tables
err = p.createTables()
if err != nil {
return fmt.Errorf("Failed to create tables - %v", err)
}

return nil
}

func (p postgresDb) addRecord(resource shaman.Resource) error {
resources, err := p.listRecords()
if err != nil {
return err
}

for i := range resources {
if resources[i].Domain == resource.Domain {
// if domains match, check address
for k := range resources[i].Records {
next:
for j := range resource.Records {
// check if the record exists...
if resource.Records[j].RType == resources[i].Records[k].RType &&
resource.Records[j].Address == resources[i].Records[k].Address &&
resource.Records[j].Class == resources[i].Records[k].Class {
// if so, skip
config.Log.Trace("Record exists in persistent, skipping...")
resource.Records = append(resource.Records[:i], resource.Records[i+1:]...)
goto next
}
}
}
}
}

// add records
for i := range resource.Records {
config.Log.Trace("Adding record to database...")
_, err = p.pg.Exec(fmt.Sprintf(`
INSERT INTO records(domain, address, ttl, class, type)
VALUES('%v', '%v', '%v', '%v', '%v')`,
resource.Domain, resource.Records[i].Address, resource.Records[i].TTL,
resource.Records[i].Class, resource.Records[i].RType))
if err != nil {
return fmt.Errorf("Failed to insert into records table - %v", err)
}
}

return nil
}

func (p postgresDb) getRecord(domain string) (*shaman.Resource, error) {
// read from records table
rows, err := p.pg.Query(fmt.Sprintf("SELECT address, ttl, class, type FROM records WHERE domain = '%v'", domain))
if err != nil {
return nil, fmt.Errorf("Failed to select from records table - %v", err)
}
defer rows.Close()

records := make([]shaman.Record, 0, 0)

// get data
for rows.Next() {
rcrd := shaman.Record{}
err = rows.Scan(&rcrd.Address, &rcrd.TTL, &rcrd.Class, &rcrd.RType)
if err != nil {
return nil, fmt.Errorf("Failed to save results into record - %v", err)
}

records = append(records, rcrd)
}

// check for errors
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("Error with results - %v", err)
}

if len(records) == 0 {
return nil, errNoRecordError
}

return &shaman.Resource{Domain: domain, Records: records}, nil
}

func (p postgresDb) updateRecord(domain string, resource shaman.Resource) error {
// delete old from records
err := p.deleteRecord(domain)
if err != nil {
return fmt.Errorf("Failed to clean old records - %v", err)
}

return p.addRecord(resource)
}

func (p postgresDb) deleteRecord(domain string) error {
_, err := p.pg.Exec(fmt.Sprintf(`DELETE FROM records WHERE domain = '%v'`, domain))
if err != nil {
return fmt.Errorf("Failed to delete from records table - %v", err)
}

return nil
}

func (p postgresDb) resetRecords(resources []shaman.Resource) error {
// truncate records table
_, err := p.pg.Exec("TRUNCATE records")
if err != nil {
return fmt.Errorf("Failed to truncate records table - %v", err)
}
for i := range resources {
err = p.addRecord(resources[i]) // prevents duplicates
if err != nil {
return fmt.Errorf("Failed to save records - %v", err)
}
}
return nil
}

func (p postgresDb) listRecords() ([]shaman.Resource, error) {
// read from records table
rows, err := p.pg.Query("SELECT DISTINCT domain FROM records")
if err != nil {
return nil, fmt.Errorf("Failed to select from records table - %v", err)
}
defer rows.Close()

resources := make([]shaman.Resource, 0)

// get data
for rows.Next() {
var domain string
err = rows.Scan(&domain)
if err != nil {
return nil, fmt.Errorf("Failed to save domain - %v", err)
}
resource, err := p.getRecord(domain)
if err != nil {
return nil, fmt.Errorf("Failed to get record for domain - %v", err)
}

resources = append(resources, *resource)
}

// check for errors
if err = rows.Err(); err != nil {
return nil, fmt.Errorf("Error with results - %v", err)
}
return resources, nil
}
Loading

0 comments on commit 01c36c0

Please sign in to comment.