Skip to content

Commit

Permalink
v0.0.1
Browse files Browse the repository at this point in the history
  - format Get-DnsServerResourceRecord and convert
    to internal dns Record object
  - add/remove A and Txt records
  - query dns records
  - unit tests are written
  - python type hint is added
  - Python Packaging Authority LICENCE added
  - Makefile for packaging and etc.
  • Loading branch information
bilalekremharmansa committed Oct 28, 2020
1 parent 6446b93 commit 4579f4f
Show file tree
Hide file tree
Showing 17 changed files with 420 additions and 84 deletions.
19 changes: 19 additions & 0 deletions LICENCE
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Copyright (c) 2018 The Python Packaging Authority

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
python = python
ifdef PYTHON
python = ${PYTHON}
endif

twine = ${python} -m twine
pip = ${python} -m pip

upload: build
@${twine} upload dist/* -r testpypi

build:
rm -rf build
rm -rf dist
${python} setup.py sdist
${python} setup.py bdist_wheel
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## microsoftdnsserver-py

microsoftdnsserver-py is a wrapper Python library for [DnsServer](https://docs.microsoft.com/en-us/powershell/module/dnsserver/?view=win10-ps) module.

Subprocess module is used to perform process calls to interact with DnsServer module.

## Features
- Convenient as a Python library
- Supports A and Txt record
- Query DNS records

## Installation

```shell
```

## Limitations
- Libray is not able to work remotely, currently, it is able to call DnsServer module on localhost.
Since, the remote session feature is not on the table, the software that uses this module
must be installed on windows server where Microsoft Dns Server is located.
10 changes: 0 additions & 10 deletions main.py

This file was deleted.

39 changes: 25 additions & 14 deletions microsoftdnsserver/command_runner/powershell_runner.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import subprocess
import sys

from .runner import Command, CommandRunner, Result
from ..util import logger

DEBUG = True

POWERSHELL_EXE_PATH="C:\Windows\syswow64\WindowsPowerShell\\v1.0\powershell.exe"
DEFAULT_POWERSHELL_EXE_PATH = "C:\Windows\syswow64\WindowsPowerShell\\v1.0\powershell.exe"


class PowerShellCommand(Command):

def __init__(self, cmdlet, *flags, **args):
def __init__(self, cmdlet: str, *flags, **args):
super().__init__()

self.cmdlet = cmdlet
self.flags = flags
self.args = args

def prepareCommand(self):
cmd = [POWERSHELL_EXE_PATH, self.cmdlet]
cmd = [self.cmdlet]

# add flags, ie -Force
for flag in self.flags:
Expand All @@ -39,26 +39,37 @@ def _postProcessResult(self):

class PowerShellRunner(CommandRunner):

def run(self, command):
assert (command, PowerShellCommand)
def __init__(self, powerShellPath: str = None):
self.logger = logger.createLogger("PowerShellRunner")

self.powerShellPath = powerShellPath
if powerShellPath is None:
self.powerShellPath = DEFAULT_POWERSHELL_EXE_PATH

def run(self, command: PowerShellCommand) -> Result:
assert isinstance(command, PowerShellCommand)

cmd = command.prepareCommand()
cmd.insert(0, self.powerShellPath)

if DEBUG:
print(' '.join(cmd))
return
self.logger.debug("Running: [%s]" % ' '.join(cmd))

proc = subprocess.Popen(cmd, stdout=sys.stdout)
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
try:
out, _ = proc.communicate(timeout=60)
out, err = proc.communicate(timeout=60)
except:
proc.kill()
out, _ = proc.communicate()
out, err = proc.communicate()
finally:
pass

out = out.decode('utf-8')
err = err.decode('utf-8')

self.logger.debug("Returned: \n\tout:[%s], \n\terr:[%s]" % (out, err))

success = proc.returncode == 0
return Result(success, proc.returncode, out)
return Result(success, proc.returncode, out, err)



5 changes: 3 additions & 2 deletions microsoftdnsserver/command_runner/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,14 @@ def prepareCommand(self):

class CommandRunner(object):

def run(self, cmd):
def run(self, cmd: Command):
raise MethodNotImplementedError()


class Result(object):

def __init__(self, success, code, out):
def __init__(self, success: bool, code: int, out: str, err: str):
self.success = success
self.code = code
self.out = out
self.err = err
15 changes: 12 additions & 3 deletions microsoftdnsserver/dns/base.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from typing import List

from microsoftdnsserver.dns.record import Record, RecordType
from microsoftdnsserver.exception.exception_common import MethodNotImplementedError


Expand All @@ -6,11 +9,17 @@ class DNSService(object):
def __init__(self):
pass

def getDNSRecords(self, zone, name, recordType):
def getDNSRecords(self, zone: str, name: str, recordType: RecordType) -> List[Record]:
raise MethodNotImplementedError()

def addARecord(self, zone: str, name: str, ip: str, ttl: str) -> bool:
raise MethodNotImplementedError()

def addARecord(self, zone, name, ip, ttl, ageRecord):
def removeARecord(self, zone: str, name: str) -> bool:
raise MethodNotImplementedError()

def removeARecord(self, zone, name, recordData):
def addTxtRecord(self, zone: str, name: str, content, ttl: str) -> bool:
raise MethodNotImplementedError()

def removeTxtRecord(self, zone: str, name: str) -> bool:
raise MethodNotImplementedError()
67 changes: 33 additions & 34 deletions microsoftdnsserver/dns/dnsserver.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import logging
import json

from microsoftdnsserver.command_runner.powershell_runner import PowerShellCommand
from microsoftdnsserver.command_runner.runner import Command, CommandRunner
from microsoftdnsserver.command_runner.powershell_runner import PowerShellCommand, PowerShellRunner
from .base import DNSService
from ..util import dns_server_utils
from .record import RecordType
from ..util import dns_server_utils, logger


class DnsServerModule(DNSService):
"""
Expand All @@ -11,26 +14,15 @@ class DnsServerModule(DNSService):
https://docs.microsoft.com/en-us/powershell/module/dnsserver/?view=win10-ps
"""

def __init__(self, runner):
def __init__(self, runner: CommandRunner = None):
super().__init__()
self.runner = runner
if runner is None:
self.runner = PowerShellRunner()

self.logger = None
pass

def _initLogger(self):
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
handler.setFormatter(formatter)

logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.DEBUG)

self.logger = logger

self.logger = logger.createLogger("DnsServer")

def getDNSRecords(self, zone, name=None, recordType=None):
def getDNSRecords(self, zone: str, name: str = None, recordType: RecordType = None):
""" uses Get-DnsServerResourceRecord cmdlet to get records in a zone """

args = {
Expand All @@ -40,13 +32,15 @@ def getDNSRecords(self, zone, name=None, recordType=None):
if name:
args['Name'] = name
if recordType:
args['RRType'] = recordType
args['RRType'] = recordType.value

command = PowerShellCommand('Get-DnsServerResourceRecord', **args)
self.run(command)
result = self.run(command)

jsonResult = json.loads(result.out)
return dns_server_utils.formatDnsServerResult(zone, jsonResult)

def addARecord(self, zone, name, ip, ttl='1h', ageRecord=False):
def addARecord(self, zone: str, name: str, ip: str, ttl: str = '1h'):
""" uses Add-DnsServerResourceRecordA cmdlet to add a resource in a zone """

command = PowerShellCommand(
Expand All @@ -58,9 +52,10 @@ def addARecord(self, zone, name, ip, ttl='1h', ageRecord=False):
TimeToLive=dns_server_utils.formatTtl(ttl)
)

self.run(command)
result = self.run(command)
return result.success

def removeARecord(self, zone, name, recordData=None):
def removeARecord(self, zone: str, name: str):
""" uses Remove-DnsServerResourceRecord cmdlet to remove a record in a zone """

args = {
Expand All @@ -69,17 +64,17 @@ def removeARecord(self, zone, name, recordData=None):
}
if name:
args['Name'] = name
if recordData:
args['RecordData'] = recordData

flags = ['Force']

command = PowerShellCommand('Remove-DnsServerResourceRecord', *flags, **args)
self.run(command)
result = self.run(command)

return result

# ---

def addTxtRecord(self, zone, name, content, ttl='1h'):
def addTxtRecord(self, zone: str, name: str, content: str, ttl: str = '1h'):
""" uses Add-DnsServerResourceRecord cmdlet to add txt resource in a zone """

command = PowerShellCommand(
Expand All @@ -92,9 +87,11 @@ def addTxtRecord(self, zone, name, content, ttl='1h'):
TimeToLive=dns_server_utils.formatTtl(ttl)
)

self.run(command)
result = self.run(command)

return result.success

def removeTxtRecord(self, zone, name, recordData=None):
def removeTxtRecord(self, zone: str, name: str):
""" uses Remove-DnsServerResourceRecord cmdlet to remove txt record in a zone """

args = {
Expand All @@ -103,22 +100,24 @@ def removeTxtRecord(self, zone, name, recordData=None):
}
if name:
args['Name'] = name
if recordData:
args['RecordData'] = recordData

flags = ['Force']

command = PowerShellCommand('Remove-DnsServerResourceRecord', *flags, **args)
self.run(command)
result = self.run(command)

return result.success

# --

def run(self, command):
def run(self, command: Command):
result = self.runner.run(command)

if not result.success:
self.logger.error("Command failed [%s]" % command.prepareCommand())

return result

def isDnsServerModuleInstalled(self):
cmdlet = "Get-Module DNSServer -ListAvailable"
result = self.runner.run(cmdlet)
Expand Down
25 changes: 22 additions & 3 deletions microsoftdnsserver/dns/record.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,27 @@
from enum import Enum


class RecordType(Enum):
A = 'A'
TXT = 'Txt'

@staticmethod
def list():
return [t.value for t in RecordType]

@staticmethod
def value_of(value):
return next((t for t in RecordType if t.value == value))


class Record(object):

def __init__(self, zone, name, type, content, ttl=1):
def __init__(self, zone: str, name: str, recordType: RecordType, content: str, ttl: str = '1h'):
self.zone = zone
self.name = name
self.type = type
self.type = recordType
self.content = content
self.ttl = ttl
self.ttl = ttl

def __repr__(self):
return '[%s] record - zone: [%s], name: [%s]' % (self.type, self.zone, self.name)
Loading

0 comments on commit 4579f4f

Please sign in to comment.