Skip to content

Latest commit

 

History

History
718 lines (465 loc) · 27.9 KB

README.md

File metadata and controls

718 lines (465 loc) · 27.9 KB

hetzner-dns-tools

"A simple Hetzner DNS API client for Python and Bash."

This library makes it easier to work with Hetzner's DNS API, namely Zones and Records.

Project Status: All basic CRUD functionality is complete. Tests still need to be written. Bulk record CREATE options not (yet) supported.

To be specific, hetzner-dns-tools makes it easier to manage your zones/records by name instead of having to get the ID first (although you can do that as well). Also, it allows you to retrieve only the IDs if needed, without having to manually parse the JSON first.

Limitations: hetzner-dns-tools does not currently do bulk operations other than bulk record deletion, and it does not work with query params (it would be easy to add if you are so inclined). Pull requests and forks are welcomed! :)

These tools are made with Python and are designed to be used in Bash or Python.

All examples in this README assume you are in the root directory of this project when running commands.

Table of Contents

Setup

  • Ensure you have a Hetzner DNS API token.
  • Install the hetzner-dns-tools package:
    • Via pip (recommended):
      • pip install hetzner-dns-tools
    • Via GitHub:
      • Clone the hetzner-dns-tools repo: git clone https://github.com/arcanemachine/hetzner-dns-tools
      • Navigate to the root folder of the project.
      • Ensure the python3 build module is installed: python3 -m pip install build
      • Build the project: python3 -m build
      • Install the package: python3 -m pip install .

Basic usage example: HETZNER_DNS_TOKEN=your-hetzner-dns-token hetzner-dns-tools zone list (Note: To prevent sensitive data from being saved in your Bash history, ensure that this command begins with a space, or set the environment variable somewhere else.

Run In Docker

  • Install Docker Engine.
  • Start the container and view the logs while the project builds: docker run --name hetzner-dns-tools -dt arcanemachine/hetzner-dns-tools && docker logs -f hetzner-dns-tools
  • After the project finishes building* press Ctrl + C to exit the logs, and open a bash shell in the same container: docker exec -it hetzner-dns-tools bash
    • *You can exit the logs before the build completes. However, hetzner-dns-tools will not be available from the command line until the build is complete.
  • From this shell, you can run hetzner-dns-tools or open python3 and import hetzner_dns_tools.
  • When you are finished, you can stop and destroy the container with docker stop -t 1 hetzner-dns-tools and docker rm hetzner-dns-tools.

Note: This library allows indirect lookups to be performed by domain name or other parameters, which will result in multiple requests being issued. To decrease the run time, use zone IDs and record IDs whenever possible.

Setting Parameters

There are two methods of setting parameters using this library:

  • Bash environment variables
    • e.g. HETZNER_DNS_TOKEN=your-hetzner-token hetzner-dns-tools zone list*
  • Python arguments
    • e.g. zone_list(hetzner_dns_token='your-hetzner-token')

*If you are using this library via Bash, you may want to look into something like direnv in order to prevent your DNS token from leaking into your ~/.bash_history.

Any arguments used when calling a Python function will override the values of any environment variables.

Please note that the name parameter is used in Hetzner's DNS API calls (specifically in zone_create and record_create), while the zone_name parameter is only used in hetzner-dns-tools.

How to Use This Library

All API calls require a hetzner_dns_token parameter to be set.

In Bash

Simply execute the file you want to run. For example: hetzner-dns-tools zone list (Ensure that you set HETZNER_DNS_TOKEN environment variable before every command. Read this section to learn more about setting environment variables.) Data can be added to the command by setting environment variables, e.g. NAME=your-domain.com hetzner-dns-tools zone create.

To get the Python docstring (ie. help file) for a function, set the environment variable SHOW_HELP to a truthy value, e.g. SHOW_HELP=1 hetzner-dns-tools zone get

To know whether a script executed successfully or not, run echo $? after running a command. If the value of $? is 0, the script executed successfully. If the value of $? is 1, the script exited with an error.

Most errors that occur in the Python code (e.g. if you forget to set an environment variable) will raise an exception and print a stack trace in the console. Other errors will begin with Error: and contain a description of the error.

In Python

from hetzner_dns_tools.zone_list import zone_list

# THIS EXAMPLE WILL NOT WORK IF YOU HAVEN'T CREATED ANY HETZNER DNS ZONES

# get zone list
dns_zones = zone_list()['zones']

# parse ID from first zone
your_zone_id = dns_zones[0]['id']

print(your_zone_id)

Project Structure

These tools are namespaced by feature into modules:

  • zone_create, zone_get, and zone_delete
  • record_create, record_get, and record_delete

e.g. Running hetzner-dns-tools zone list in Bash will list all available DNS zones. (Make sure to pass your HETZNER_DNS_TOKEN as an environment variable)

Converting Results to Human-Readable Output

The default output is nearly impossible for humans to read. Here's how to format it so it looks better:

In Bash

This method requires you to have npm installed:

  • Install the npm package json globally:
    • npm i -g json
  • Pipe the output of a command to the newly-installed json package:
    • hetzner-dns-tools zone listjson

If you know of a better method (particularly one that doesn't require npm to be installed), please submit a ticket.

In Python

import json
from hetzner_dns_tools.zone_list import zone_list

# get zone list
dns_zones = zone_list()['zones']

# make it readable
readable_dns_zones = json.dumps(dns_zones, indent=2)

print(readable_dns_zones)

Usage Guide

Zone Functions

Record Functions

This section assumes that you have exported the HETZNER_DNS_TOKEN environment variable before running any Bash commands. Read the section on setting Bash environment variables if you don't know how to do this.)

In Bash, results will be returned as a condensed JSON-formatted string, except when string values (e.g. zone IDs) are requested, or when DELETE actions are performed (will return 'OK' if successful).

In Python, results will be returned as a dictionary, except when string values (e.g. zone IDs) are requested, or when DELETE actions are performed (will return 'OK' if successful.

Zones

zone_list

Get list of all zones. (Hetzner Docs API - Get All Zones)

In Bash

hetzner-dns-tools zone list

In Python

from hetzner_dns_tools.zone_list import zone_list

your_zones = zone_list()

print(your_zones)

zone_create

Create a new zone. (Hetzner DNS API Docs - Create Zone)

Required Parameters: name/zone_name (name and zone_name are interchangeable)

Optional Parameters: ttl

NOTE: zone_create and zone_delete allow the name and zone_name parameters (or the NAME and ZONE_NAME environment variables) to be used interchangeably. Note that the name parameter is used in Hetzner's API, but zone_name is commonly used in this library, so I allow both to be used to reduce the cognitive burden of having to switch from one to the other.

In Bash

To return all data for the zone: NAME=your-domain.com hetzner-dns-tools zone create or ZONE_NAME=your-domain.com hetzner-dns-tools zone create

To return just the zone ID: NAME=your-domain.com ID_ONLY=1 hetzner-dns-tools zone create

To create a new zone with a custom TTL (default: 86400): NAME=your-domain.com TTL=57600 hetzner-dns-tools zone create

In Python

To get all data for the new zone:

from hetzner_dns_tools.zone_create import zone_create

# create a new zone and return all zone data
new_zone = zone_create(hetzner_dns_token='your-token',
                       name='your-domain.com')  # can also use zone_name

# print the name of the new zone
print(new_zone['zone']['name'])  # 'your-domain.com'

To return just the ID for the new zone:

from hetzner_dns_tools.zone_create import zone_create

# create a new zone and return just the zone_id
new_zone_id = zone_create(hetzner_dns_token='your-token',
                          zone_name='your-domain.com',  # can also use name
                          id_only=True)

# print the ID of the new zone
print(new_zone_id)

To create a new zone with a custom TTL (default: 86400):

from hetzner_dns_tools.zone_create import zone_create

# create a new zone and return all zone data
new_zone = zone_create(hetzner_dns_token='your-token',
                       name='your-domain.com',  # can also use zone_name
                       ttl=57600)

# print the TTL of the new zone
print(new_zone['zone']['ttl'])  # 57600

zone_get

Get info about an existing zone. (Hetzner DNS API Docs - Get Zone)

Required Parameters: One of: zone_id or zone_name

Optional Parameters: id_only

In Bash

To return all data for the zone by using the zone ID: ZONE_ID=your-zone-id hetzner-dns-tools zone get

To return all data for the zone by using the zone's domain name: ZONE_NAME=your-domain.com hetzner-dns-tools zone get

To return just the zone ID by using the zone's domain name: ZONE_NAME=your-domain.com ID_ONLY=1 hetzner-dns-tools zone get

In Python

To return all data for the zone by using the zone ID:

from hetzner_dns_tools.zone_get import zone_get

zone = zone_get(hetzner_dns_token='your-token',
                zone_id='your-zone-id')

# print the name of the zone
print(zone['zone']['name'])

To return all data for the zone by using the zone's domain name:

from hetzner_dns_tools.zone_get import zone_get

zone = zone_get(hetzner_dns_token='your-token',
                zone_name='your-domain.com')

# print the ID of the zone
print(zone['zone']['id'])

To return just the zone ID by using the zone's domain name:

from hetzner_dns_tools.zone_get import zone_get

zone_id = zone_get(hetzner_dns_token='your-token',
                   zone_name='your-domain.com',
                   id_only=True)

# print the ID of the zone
print(zone_id)

zone_update

To update a zone, use zone_delete to delete a zone, and then use zone_create to create a new one. (I had some issues getting the function to work with Hetzner's backend (it wouldn't allow any changes to be made), plus the ability to delete and create records effectively makes the update function redundant. If you want to try to implement the function yourself, there is a zone_update.py file in the experimental-functions branch that may be of some help.

zone_delete

Delete an existing zone. (Hetzner DNS API Docs - Delete Zone)

Required Parameters: One of: zone_id or zone_name/name

Zones can be deleted directly using a zone_id, or can be done indirectly by using any of the Optional Parameters as a lookup.

Successful delete operations will return the string 'OK', and unsuccessful delete operations will raise a ValueError exception.

NOTE: zone_create and zone_delete allow the name and zone_name parameters (or the NAME and ZONE_NAME environment variables) to be used interchangeably. Note that the name parameter is used in Hetzner's API, but zone_name is commonly used in this library, so I allow both to be used to reduce the cognitive burden of having to switch from one to the other.

In Bash

To delete a zone by its zone ID: ZONE_ID=your-zone-id hetzner-dns-tools zone delete

To delete a zone by its zone (ie. domain) name: ZONE_NAME=your-domain.com hetzner-dns-tools zone delete or NAME=your-domain.com hetzner-dns-tools zone delete

In Python

To delete a zone by its zone ID:

from hetzner_dns_tools.zone_delete import zone_delete

zone_delete(hetzner_dns_token='your-token',
            zone_id='your-zone-id')

To delete a zone by its zone (ie. domain) name:

from hetzner_dns_tools.zone_delete import zone_delete

zone_delete(hetzner_dns_token='your-token',
            zone_name='your-domain.com')  # can also use 'name'

Records

record_list

Get list of all records. (Hetzner DNS API Docs - Get All Records)

Required Parameters: One of: zone_id or zone_name

In Bash

To return all data for all zones: hetzner-dns-tools record list

To return all data for a single zone, by zone ID: ZONE_ID=your-zone-id hetzner-dns-tools record list

To return all data for a single zone, by zone (ie. domain) name: ZONE_NAME=your-domain.com hetzner-dns-tools record list

In Python

To return all data for all zones:

from hetzner_dns_tools.record_list import record_list

records = record_list(hetzner_dns_token='your-token')

print(records)

To return all data for a single zone, by zone ID:

from hetzner_dns_tools.record_list import record_list

records = record_list(hetzner_dns_token='your-token',
                      zone_id='your-zone-id')

print(records)

To return all data for a single zone, by zone (ie. domain) name:

from hetzner_dns_tools.record_list import record_list

records = record_list(hetzner_dns_token='your-token',
                      zone_name='your-domain.com')

print(records)

record_create

Create a new record. (Hetzner DNS API Docs - Create Record)

Required Parameters: hetzner_dns_token, record_type, value, zone_id

Optional Parameters: zone_name, name*, ttl, id_only

*If name is not specified, the root value @ will be used (except for MX records).

To get the ID of the zone you want to create the record in, you can use zone_name to do an indirect lookup an obtain the zone_id. Note that doing this will result in an additional request being made.

Note: Hetzner's DNS API requires a the type parameter to specify the type of record (e.g. A, AAAA, CNAME, MX, etc.). Because the word type is a reserved keyword in Python, the record functions all use the record_type parameter instead. When using environment variables, either TYPE or RECORD_TYPE may be used interchangeably.

In Bash

To create an A record for zone ID your-zone-id with name www and value 1.1.1.1, and return all record data: ZONE_ID=your-zone-id TYPE=A NAME=www VALUE=1.1.1.1 hetzner-dns-tools record create

To return just the record ID after creating the record: ZONE_ID=your-zone-id RECORD_TYPE=A NAME=www VALUE=1.1.1.1 ID_ONLY=1 hetzner-dns-tools record create

To create a new record with a custom TTL (default is 86400), use TTL: ZONE_ID=your-zone-id TYPE=A NAME=www VALUE=1.1.1.1 TTL=57600 hetzner-dns-tools record create

To create a MX record with a target of your-domain.com and a priority of 10, both values must be entered as such in the VALUE field: ZONE_ID=your-zone-id TYPE=MX VALUE="10 your-mail-server.com" hetzner-dns-tools record create

To create a SRV record with a priority of 1, a weight of 2, at port 3 for a target your-server.com, all values must be entered in this order in the VALUE field: ZONE_ID=your-zone-id TYPE=SRV VALUE="1 2 3 your-server.com" hetzner-dns-tools record create

In Python

To create an A record for zone ID your-zone-id with name www and value 1.1.1.1, and return all record data:

from hetzner_dns_tools.record_create import record_create

# create a new record and return all record data
new_record = record_create(hetzner_dns_token='your-token',
                           zone_id='your-zone-id',
                           record_type='A',
                           name='www',
                           value='1.1.1.1')

# print the record's 'created' value
print(new_record['record']['created'])

To return just the record ID after creating the record:

from hetzner_dns_tools.record_create import record_create

# create a new record and return all record data
new_record_id = record_create(hetzner_dns_token='your-token',
                              zone_id='your-zone-id',
                              record_type='A',
                              name='www',
                              value='1.1.1.1',
                              id_only=True)

# print the new record's ID
print(new_record_id)

To create a new MX record with server your-mail-server.com and priority 10:

from hetzner_dns_tools.record_create import record_create

# create a new record and return all record data
new_record = record_create(hetzner_dns_token='your-token',
                           zone_id='your-zone-id',
                           record_type='MX',
                           value='10 your.mail-server.com')

To create a SRV record with a priority of 1, a weight of 2, at port 3 for a target your-server.com:

from hetzner_dns_tools.record_create import record_create

# create a new record and return all record data
new_record = record_create(hetzner_dns_token='your-token',
                           zone_id='your-zone-id',
                           record_type='SRV',
                           value='1 2 3 your.server.com')

record_get

Get info about an existing record. (Hetzner DNS API Docs - Get Record)

Required* Parameters: One of: record_id or zone_id or zone_name

Optional Parameters:
 Filters: record_type, name, value, ttl
 Formats: id_only
 Options: first_record_only, allow_multiple_records, search_all_zones*

*If the search_all_zones parameter is given a truthy value, then you do not need to include any of the Required Parameters, as their purpose is to ensure that records are only returned for a single zone.

Note: This function will raise an exception if multiple records are returned, *unless* the first_record_only *or* allow_multiple_records parameters are truthy.

Options

These parameters can be given truthy values to enable them:

allow_multiple_records - If multiple records are returned, return all of them. first_record_only - Return only the first record found. (There is no guarantee of any ordering.) search_all_zones - Allow records to be returned from all zones. No required parameters are needed when using this option. id_only - Returns only the ID of the given record. If this argument and allow_multiple_records are both truthy, a list of record IDs will be returned.

In Bash

To return all data for single record via the record's ID: RECORD_ID=your-record-id hetzner-dns-tools record get

To return all MX records for a zone by using a zone ID as a lookup: ZONE_ID=your-zone-id TYPE=MX ALLOW_MULTIPLE_RECORDS=1 hetzner-dns-tools record get

To return a zone's A record with a name of 'www' by using a zone (ie. domain) name as a lookup: ZONE_NAME=your-domain.com TYPE=A NAME=www hetzner-dns-tools record get

To return all record IDs for a zone by using a zone ID as a lookup: ZONE_ID=your-zone-id ALLOW_MULTIPLE_RECORDS=1 ID_ONLY=1 hetzner-dns-tools record get

To return all A records from all zones with a name of '@' (root): TYPE=A NAME="@" SEARCH_ALL_ZONES=1 ALLOW_MULTIPLE_RECORDS=1 hetzner-dns-tools record get

To return the first returned A record with a value of 1.2.3.4 and a TTL of 57600 by using a zone (ie. domain) name as a lookup: ZONE_NAME=your-domain.com TYPE=A VALUE=1.2.3.4 TTL=57600 FIRST_RECORD_ONLY=1 hetzner-dns-tools record get

In Python

To return all data for single record via the record's ID:

from hetzner_dns_tools.record_get import record_get

record = record_get(hetzner_dns_token='your-token',
                    record_id='your-record-id')

To return all MX records for a zone by using a zone ID as a lookup:

from hetzner_dns_tools.record_get import record_get

records = record_get(hetzner_dns_token='your-token',
                     zone_id='your-zone-id',
                     record_type='MX',
                     allow_multiple_records=True)

To return a zone's A record with a name of 'www' by using a zone (ie. domain) name as a lookup:

from hetzner_dns_tools.record_get import record_get

record = record_get(hetzner_dns_token='your-token',
                    zone_id='your-zone-id',
                    record_type='A',
                    name='www')

To return all MX records for a zone by using a zone ID as a lookup:

from hetzner_dns_tools.record_get import record_get

records = record_get(hetzner_dns_token='your-token',
                     zone_id='your-zone-id',
                     record_type='MX',
                     allow_multiple_records=True)

To return all record IDs for a zone by using a zone ID as a lookup:

from hetzner_dns_tools.record_get import record_get

# this value will contain a list of zone IDs
record_ids = record_get(hetzner_dns_token='your-token',
                        zone_id='your-zone-id',
                        allow_multiple_records=True,
                        id_only=True)

To return all record IDs for a zone by using a zone ID as a lookup:

from hetzner_dns_tools.record_get import record_get

# this value will contain a list of zone IDs
record_ids = record_get(hetzner_dns_token='your-token',
                        zone_id='your-zone-id',
                        allow_multiple_records=True,
                        id_only=True)

To return all A records from all zones with a name of '@' (root):

from hetzner_dns_tools.record_get import record_get

# this value will contain a list of zone IDs
record_ids = record_get(hetzner_dns_token='your-token',
                        search_all_zones=True,
                        record_type='A',
                        name='@',
                        allow_multiple_records=True)

To return the first record with a value of 1.2.3.4 by using a zone (ie. domain) name as a lookup:

from hetzner_dns_tools.record_get import record_get

# this value will contain a list of zone IDs
record_ids = record_get(hetzner_dns_token='your-token',
                        zone_name='your-domain.com',
                        value='1.2.3.4',
                        first_record_only=True)

record_update

As with the zone modules, you can use record_delete and record_create to update a record. This library does not currently have a native record_update module.

record_delete

Delete an existing record. (Hetzner DNS API Docs - Delete Record)

Required* Parameters: One of: record_id or zone_id or zone_name

Optional Parameters:
 Filters: record_type, name, value (but not ttl*)
 Options: delete_multiple_records, first_record_only, search_all_zones**

Records can be deleted directly using a record_id, or can be done indirectly by using any of the Optional Parameters as a lookup.

Successful delete operations will return the string 'OK', and unsuccessful delete operations will raise a ValueError exception.

*Due to how record_list returns its results, ttl is not an available option for filtering records

**If the search_all_zones parameter is given a truthy value, then you do not need to include any of the Required Parameters, as their purpose is to ensure that records are only returned for a single zone.

Note: This function will raise an exception if multiple records are returned, *unless* the first_record_only *or* allow_multiple_records parameters are truthy.

Options

These parameters can be given truthy values to enable them:

delete_multiple_records - Delete all matching records, even if there is more than one record returned. first_record_only - Delete only the first record returned. (There is no guarantee of any ordering.) search_all_zones - Allow records to be returned from all zones. None of "required" parameters are needed when using this option.

In Bash

To delete a record by using its record ID: RECORD_ID=your-record-id hetzner-dns-tools record delete

To delete a zone's A record with a name of 'www' by using a zone (ie. domain) name as a lookup: ZONE_NAME=your-domain.com TYPE=A NAME=www hetzner-dns-tools record delete

To delete all MX records for a zone by using a zone ID as a lookup: ZONE_ID=your-zone-id TYPE=MX DELETE_MULTIPLE_RECORDS=1 hetzner-dns-tools record delete

To delete all CNAME records from all zones: TYPE=CNAME SEARCH_ALL_ZONES=1 DELETE_MULTIPLE_RECORDS=1 hetzner-dns-tools record delete

To delete the first returned A record with a value of 1.2.3.4 by using a zone (ie. domain) name as a lookup: ZONE_NAME=your-domain.com TYPE=A VALUE=1.2.3.4 FIRST_RECORD_ONLY=1 hetzner-dns-tools record delete

In Python

To delete a record by using the record ID:

from hetzner_dns_tools.record_delete import record_delete

record_delete(hetzner_dns_token='your-token',
              record_id='your-record-id')

To delete a zone's A record with a name of 'www' by using a zone (ie. domain) name as a lookup:

from hetzner_dns_tools.record_delete import record_delete

record_delete(hetzner_dns_token='your-token',
              zone_name='your-domain.com',
              record_type='A',
              name='www')

To delete all MX records for a zone by using a zone ID as a lookup:

from hetzner_dns_tools.record_delete import record_delete

record_delete(hetzner_dns_token='your-token',
              zone_id='your-zone-id',
              record_type='MX',
              delete_multiple_records=True)

To delete all A records from all zones with a name of '@' (root):

from hetzner_dns_tools.record_delete import record_delete

# will raise ValueError return 'OK' if delete operation was successful
record_delete(hetzner_dns_token='your-token',
              record_type='A',
              name='@',
              search_all_zones=True,
              delete_multiple_records=True)

To delete the first returned A record with a value of 1.2.3.4 by using a zone (ie. domain) name as a lookup:

from hetzner_dns_tools.record_delete import record_delete

# will raise ValueError return 'OK' if delete operation was successful
record_delete(hetzner_dns_token='your-token',
              zone_name='your-domain.com',
              record_type='A',
              value='1.2.3.4',
              first_record_only=True)



(c) 2022 arcanemachine. Freely distributed under the terms of the MIT Licence.