Skip to content

Commit

Permalink
Rewrite to dependency injection
Browse files Browse the repository at this point in the history
  • Loading branch information
ekohl committed Jan 19, 2016
1 parent 8100a4a commit f199bc9
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 155 deletions.
13 changes: 11 additions & 2 deletions README.md
Expand Up @@ -7,20 +7,29 @@ This plugin adds a new DNS provider for managing records in PowerDNS.
See [How_to_Install_a_Smart-Proxy_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Smart-Proxy_Plugin)
for how to install Smart Proxy plugins

This plugin is compatible with Smart Proxy 1.10 or higher.
This plugin is compatible with Smart Proxy 1.11 or higher.

When installing using "gem", make sure to install the bundle file:

echo "gem 'smart_proxy_dns_powerdns'" > /usr/share/foreman-proxy/bundler.d/dns_powerdns.rb

## Upgrading

Per version 0.2.0 the backend is a required parameter.

## Configuration

To enable this DNS provider, edit `/etc/foreman-proxy/settings.d/dns.yml` and set:

:use_provider: dns_powerdns

Configuration options for this plugin are in `/etc/foreman-proxy/settings.d/dns_powerdns.yml` and include:
Configuration options for this plugin are in `/etc/foreman-proxy/settings.d/dns_powerdns.yml`.

### MySQL

To use MySQL, set the following parameters:

:powerdns_backend: 'mysql'
:powerdns_mysql_hostname: 'localhost'
:powerdns_mysql_username: 'powerdns'
:powerdns_mysql_password: ''
Expand Down
24 changes: 24 additions & 0 deletions lib/smart_proxy_dns_powerdns/backend/dummy.rb
@@ -0,0 +1,24 @@
module Proxy::Dns::Powerdns::Backend
class Dummy < ::Proxy::Dns::Powerdns::Record

def initialize(a_server = nil, a_ttl = nil)
super(a_server, a_ttl)
end

def get_zone name
{
'id' => 1,
'name' => name.partition('.')[2]
}
end

def create_record domain_id, name, ttl, content, type
false
end

def delete_record domain_id, name, type
false
end
end
end

49 changes: 49 additions & 0 deletions lib/smart_proxy_dns_powerdns/backend/mysql.rb
@@ -0,0 +1,49 @@
require 'mysql2'

module Proxy::Dns::Powerdns::Backend
class Mysql < ::Proxy::Dns::Powerdns::Record

attr_reader :hostname, :username, :password, :database

def initialize(a_server = nil, a_ttl = nil)
@hostname = Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_hostname || 'localhost'
@username = Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_username
@password = Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_password
@database = Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_database

super(a_server, a_ttl)
end

def connection
@connection ||= Mysql2::Client.new(:host => hostname, :username => username, :password => password, :database => database)
end

def get_zone name
domain = nil

name = connection.escape(name)
connection.query("SELECT LENGTH(name) domain_length, id, name FROM domains WHERE '#{name}' LIKE CONCAT('%%.', name) ORDER BY domain_length DESC LIMIT 1").each do |row|
domain = row
end

raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS." unless domain

domain
end

def create_record domain_id, name, ttl, content, type
name = connection.escape(name)
content = connection.escape(content)
type = connection.escape(type)
connection.query("INSERT INTO records (domain_id, name, ttl, content, type) VALUES (#{domain_id}, '#{name}', #{ttl.to_i}, '#{content}', '#{type}')")
true
end

def delete_record domain_id, name, type
name = connection.escape(name)
type = connection.escape(type)
connection.query("DELETE FROM records WHERE domain_id=#{domain_id} AND name='#{name}' AND type='#{type}'")
true
end
end
end
12 changes: 12 additions & 0 deletions lib/smart_proxy_dns_powerdns/dependencies.rb
@@ -0,0 +1,12 @@
require 'dns_common/dependency_injection/dependencies'

class Proxy::Dns::DependencyInjection::Dependencies
case Proxy::Dns::Powerdns::Plugin.settings.powerdns_backend
when 'mysql'
require 'smart_proxy_dns_powerdns/backend/mysql'
dependency :dns_provider, Proxy::Dns::Powerdns::Backend::Mysql
when 'dummy'
require 'smart_proxy_dns_powerdns/backend/dummy'
dependency :dns_provider, Proxy::Dns::Powerdns::Backend::Dummy
end
end
@@ -0,0 +1,30 @@
require 'smart_proxy_dns_powerdns/dns_powerdns_plugin'

module Proxy::Dns::Powerdns
class ConfigurationValidator
def validate_settings!(settings)
validate_choice(settings, :powerdns_backend, ['mysql', 'dummy'])

case settings[:powerdns_backend]
when 'mysql'
validate_presence(settings, [:powerdns_mysql_username, :powerdns_mysql_password, :powerdns_mysql_database])
end
end

def validate_choice(settings, setting, choices)
value = settings[setting]
unless choices.include?(value)
raise ::Proxy::Error::ConfigurationError, "Parameter '#{setting}' is expected to be one of #{choices.join(",")}"
end
true
end

def validate_presence(settings, names)
names.each do |name|
value = settings[name]
raise ::Proxy::Error::ConfigurationError, "Parameter '#{name}' is expected to have a non-empty value" if value.nil? || value.to_s.empty?
end
true
end
end
end
123 changes: 38 additions & 85 deletions lib/smart_proxy_dns_powerdns/dns_powerdns_main.rb
@@ -1,120 +1,73 @@
require 'dns/dns'
require 'dns_common/dns_common'
require 'ipaddr'
require 'mysql2'

module Proxy::Dns::Powerdns
class Record < ::Proxy::Dns::Record
include Proxy::Log
include Proxy::Util

attr_reader :mysql_connection, :powerdns_pdnssec
attr_reader :pdnssec

def self.record(attrs = {})
new(attrs.merge(
:powerdns_mysql_hostname => ::Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_hostname,
:powerdns_mysql_username => ::Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_username,
:powerdns_mysql_password => ::Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_password,
:powerdns_mysql_database => ::Proxy::Dns::Powerdns::Plugin.settings.powerdns_mysql_database,
:powerdns_pdnssec => ::Proxy::Dns::Powerdns::Plugin.settings.powerdns_pdnssec
))
def initialize(a_server = nil, a_ttl = nil)
@pdnssec = Proxy::Dns::Powerdns::Plugin.settings.powerdns_pdnssec
super(a_server, a_ttl || Proxy::Dns::Plugin.settings.dns_ttl)
end

def initialize options = {}
raise "dns_powerdns provider needs 'powerdns_mysql_hostname' option" unless options[:powerdns_mysql_hostname]
raise "dns_powerdns provider needs 'powerdns_mysql_username' option" unless options[:powerdns_mysql_username]
raise "dns_powerdns provider needs 'powerdns_mysql_password' option" unless options[:powerdns_mysql_password]
raise "dns_powerdns provider needs 'powerdns_mysql_database' option" unless options[:powerdns_mysql_database]
@mysql_connection = Mysql2::Client.new(
:host => options[:powerdns_mysql_hostname],
:username => options[:powerdns_mysql_username],
:password => options[:powerdns_mysql_password],
:database => options[:powerdns_mysql_database]
)

@powerdns_pdnssec = options[:powerdns_pdnssec] || false

# Normalize the somewhat weird PTR API spec to name / content
case options[:type]
when "PTR"
if options[:value] =~ /\.(in-addr|ip6)\.arpa$/
@name = options[:value]
else
@name = IPAddr.new(options[:value]).reverse
end
@content = options[:fqdn]
else
@name = options[:fqdn]
@content = options[:value]
def create_a_record(fqdn, ip)
if found = dns_find(fqdn)
raise Proxy::Dns::Collision, "#{fqdn} is already in use by #{ip}" unless found == ip
end

super(options)
do_create(fqdn, ip, "A")
end

def create
domain_row = domain
raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS." unless domain_row

if ip = dns_find(domain_row['id'], @name)
raise Proxy::Dns::Collision, "#{@name} is already in use by #{ip}"
def create_ptr_record(fqdn, ip)
if found = dns_find(ip)
raise Proxy::Dns::Collision, "#{ip} is already in use by #{found}" unless found == fqdn
end

create_record(domain_row['id'], @name, @ttl, @content, @type)

rectify_zone(domain_row['name'])
name = IPAddr.new(ip).reverse
do_create(name, fqdn, "PTR")
end

def remove
domain_row = domain
raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS." unless domain_row

delete_record(domain_row['id'], @name, @type)

rectify_zone(domain_row['name'])
def do_create(name, value, type)
zone = get_zone(name)
create_record(zone['id'], name, type, value)
rectify_zone(zone['name'])
end

private
def domain
domain = nil
def remove_a_record(fqdn)
do_remove(fqdn, "A")
end

name = mysql_connection.escape(@name)
mysql_connection.query("SELECT LENGTH(name) domain_length, id, name FROM domains WHERE '#{name}' LIKE CONCAT('%%.', name) ORDER BY domain_length DESC LIMIT 1").each do |row|
domain = row
end
def remove_ptr_record(ip)
name = ip # Note ip is already in-addr.arpa
do_remove(name, "PTR")
end

domain
def do_remove(name, type)
zone = get_zone(name)
delete_record(zone['id'], name, type)
rectify_zone(zone['name'])
end

private
def dns_find domain_id, key
value = nil
key = mysql_connection.escape(key)
mysql_connection.query("SELECT content FROM records WHERE domain_id=#{domain_id} AND name = '#{key}' LIMIT 1").each do |row|
value = row["content"]
end
value || false
def get_zone(fqdn)
# TODO: backend specific
raise Proxy::Dns::Error, "Unable to determine zone. Zone must exist in PowerDNS."
end

private
def create_record domain_id, name, ttl, content, type
name = mysql_connection.escape(name)
content = mysql_connection.escape(content)
type = mysql_connection.escape(type)
mysql_connection.query("INSERT INTO records (domain_id, name, ttl, content, type) VALUES (#{domain_id}, '#{name}', #{ttl.to_i}, '#{content}', '#{type}')")
true
def create_record(domain_id, name, type, content)
# TODO: backend specific
end

private
def delete_record domain_id, name, type
name = mysql_connection.escape(name)
type = mysql_connection.escape(type)
mysql_connection.query("DELETE FROM records WHERE domain_id=#{domain_id} AND name='#{name}' AND type='#{type}'")
true
def delete_record(domain_id, name, type)
# TODO: backend specific
end

private
def rectify_zone domain
if @powerdns_pdnssec
%x(#{@powerdns_pdnssec} rectify-zone "#{domain}")
if @pdnssec
%x(#{@pdnssec} rectify-zone "#{domain}")

$?.exitstatus == 0
else
Expand Down
11 changes: 8 additions & 3 deletions lib/smart_proxy_dns_powerdns/dns_powerdns_plugin.rb
Expand Up @@ -2,13 +2,18 @@

module Proxy::Dns::Powerdns
class Plugin < ::Proxy::Provider
plugin :dns_powerdns, ::Proxy::Dns::Powerdns::VERSION,
:factory => proc { |attrs| ::Proxy::Dns::Powerdns::Record.record(attrs) }
plugin :dns_powerdns, ::Proxy::Dns::Powerdns::VERSION

requires :dns, '>= 1.10'
requires :dns, '>= 1.11'

validate_presence :powerdns_backend

after_activation do
require 'smart_proxy_dns_powerdns/dns_powerdns_configuration_validator'
::Proxy::Dns::Powerdns::ConfigurationValidator.new.validate_settings!

require 'smart_proxy_dns_powerdns/dns_powerdns_main'
require 'smart_proxy_dns_powerdns/dependencies'
end
end
end
57 changes: 57 additions & 0 deletions test/unit/dns_powerdns_configuration_validator_test.rb
@@ -0,0 +1,57 @@
require 'test_helper'

require 'smart_proxy_dns_powerdns/dns_powerdns_plugin'
require 'smart_proxy_dns_powerdns/dns_powerdns_configuration_validator'

class DnsPowerdnsConfigurationValidatorTest < Test::Unit::TestCase
def setup
@config_validator = Proxy::Dns::Powerdns::ConfigurationValidator.new
end

def test_initialize_missing_backend
settings = {:dns_provider => 'powerdns', :powerdns_backend => nil}

assert_raise Proxy::Error::ConfigurationError do
@config_validator.validate_settings!(settings)
end
end

def test_initialize_invalid_backend
settings = {:dns_provider => 'powerdns', :powerdns_backend => 'invalid'}

assert_raise Proxy::Error::ConfigurationError do
@config_validator.validate_settings!(settings)
end
end

def test_initialize_dummy_with_settings
settings = {:dns_provider => 'powerdns', :powerdns_backend => 'dummy'}

assert_nothing_raised do
@config_validator.validate_settings!(settings)
end
end

def test_initialize_mysql_without_settings
settings = {:dns_provider => 'powerdns', :powerdns_backend => 'mysql'}

assert_raise Proxy::Error::ConfigurationError do
@config_validator.validate_settings!(settings)
end
end

def test_initialize_mysql_with_settings
settings = {
:dns_provider => 'powerdns',
:powerdns_backend => 'mysql',
:powerdns_mysql_hostname => 'localhost',
:powerdns_mysql_username => 'username',
:powerdns_mysql_password => 'password',
:powerdns_mysql_database => 'powerdns'
}

assert_nothing_raised do
@config_validator.validate_settings!(settings)
end
end
end

0 comments on commit f199bc9

Please sign in to comment.