Automated DigitalOcean DNS ABCs: APIs, Bash, & cURL
Tags: bash, devops, linux, 100daysofcode
Published: 2020-01-15 23:20:44

***This script is an extraction of another larger single-node Kubernetes on DigitalOcean deployer that can be read here:*** {% link https://dev.to/kenmoini/all-in-one-kubernetes-host-on-digitalocean-2o93 %}

So while doing the whole ***DevOps thing*** you usually have to build automation to deploy infrastructure and platforms.  It's often not just Virtual Machines that you need to create and automate, but also DNS, networking, and more.  When using ***Ansible*** and ***Terraform*** with the larger public cloud providers this is no problem, but sometimes the more niche providers such as DigitalOcean and Linode don't have mature modules and providers in those automation platforms.

Thankfully though, pretty much everyone has mature APIs available for use, including DigitalOcean and Linode!  Yay, my friends are still cool!  So in deploying a system to DigitalOcean with Ansible or Terraform we can create and use a Bash script wrapper that has some functions that call to the DigitalOcean API and process things for us.

So let's build out a Bash script that will:

- Check to see if a DNS Zone exists for a supplied domain
- Check to see if a specific record exists in that Zone
- Create a record with a specific type and value
- Add addition or delete and overwrite records when directed/forced

{% gist https://gist.github.com/kenmoini/40d8e53212afa91a5b33e84cb2c2ac3b file=config_dns.sh %}

## What-what-whaaaaat?

Beefy bits eh?  I like providing the whole source upfront in case you wanna skip the small-talk, then stepping through the functionality to add detail.  Let's review:

- The commented `set -x` makes Bash print the invocation of each line as well, useful for debugging variables as they're expanded and passed around.
- Set a few variables, the `DO_PAT` variable is imported when previously exported and set in the shell, otherwise is defined as blank.  This is your DigitalOcean Personal Access Token, [get one here](https://www.digitalocean.com/docs/api/create-personal-access-token/).
- Bash does not hoist functions like JavaScript does so functions needed earlier need to be defined before earlier.  The `print_help` function is an example of that.
- The if/while/switch-case defines and switches through arguments passed into the script, defines needed variables where supplied
- Some arguments are required, so we check for `$domain`, `$ip_addr`, and `$record_name`
- There are some programs that are required on the system this script is run, namely `curl` and `jq` - the Bash script has a `checkForProgram` function that will check for the existence of those programs
- First up, the `checkDomain` function - this one is a pretty simple idea, before we do anything we want to make sure the domain in question has a Zone in DigitalOcean's DNS servers.  We attempt to [retrieve an existing domain](https://developers.digitalocean.com/documentation/v2/#retrieve-an-existing-domain) from the DigitalOcean API and use `jq` to check to see if the returned JSON value is null or not
- Next, we define the `checkRecord` function which takes in a supplied domain and record name+type.  So say you wanted to check to see if the domain ***example.com*** has an ***A*** type record that is named ***k8s***, or otherwise, that's to check if the ***A*** record for ***k8s.example.com*** exists or not.
- Since you can store multiple records with the same name+type, we need to have a way to delete extra records that are stale.  The `deleteRecord` function makes the API call to do exactly that with whatever DigitalOcean DNS Record ID it is passed.
- Finally, the `writeDNS` function writes a DNS Record of the specified name, type, and value to a specific domain.

The rest of the script basically just goes through some logic to check if the domain exists, if so then check if the record exists - if not, create the specified record.  There is also a forced override that will delete previous matched records and create the intended one.

## Examples

*Make sure to pass the executable bit to the script with `chmod +x ./config_dns.sh`*

Before starting, take that DigitalOcean Personal Access Token and export it to a variable:

```bash
$ export DO_PAT="your-token-here"
```

1) Say we just used Ansible or Terraform to create a new Droplet.  This new Droplet has an IP address of ***12.34.56.78*** and will be hosting our new blog at ***wombat-adventures.com*** and ***www.wombat-adventures.com***.  We can use this script like so:

```bash
$ ./config_dns.sh -d wombat-adventures.com \
                  -t "A" \
                  -r "@" \
                  -i "12.34.56.78"

$ ./config_dns.sh --domain wombat-adventures.com \
                  --type "A" \
                  --record "www" \
                  --ip "12.34.56.78"
```

2) You have a new Load Balancer with an IP Address of ***98.76.54.43*** and want to set a CNAME record at ***lb.internal.shootersbar.com***, deleting any other records that match and ensuring only one exists with the new Load Balancer IP.

```bash
$ ./config_dns.sh -d shootersbar.com \
                  -t "CNAME" \
                  -r "lb.internal" \
                  -i "98.76.54.43" \
                  --force
```

3) You need to configure GMail/GSuite MX records for mail at hyperloop-sandwiches.net

```bash
$ ./config_dns.sh -d hyperloop-sandwiches.net \
                  -t "MX" \
                  -r "@" \
                  -i "ASPMX.L.GOOGLE.COM" \
                  -p "1" \
                  --force-add

$ ./config_dns.sh -d hyperloop-sandwiches.net \
                  -t "MX" \
                  -r "@" \
                  -i "ALT1.ASPMX.L.GOOGLE.COM" \
                  --priority "5" \
                  --force-add

$ ./config_dns.sh -d hyperloop-sandwiches.net \
                  -t "MX" \
                  -r "@" \
                  -i "ALT2.ASPMX.L.GOOGLE.COM" \
                  --priority "5" \
                  --force-add

$ ./config_dns.sh -d hyperloop-sandwiches.net \
                  -t "MX" \
                  -r "@" \
                  -i "ALT3.ASPMX.L.GOOGLE.COM" \
                  --priority "10" \
                  --force-add

$ ./config_dns.sh -d hyperloop-sandwiches.net \
                  -t "MX" \
                  -r "@" \
                  -i "ALT4.ASPMX.L.GOOGLE.COM" \
                  --priority "10" \
                  --force-add
```

That's that right there, pretty simple wrapper for the DigitalOcean API to set DNS records.  It's fairly extensible, supports a wide range of records and use cases, and is pretty simple to plug into most pipelines and workflows so ***get to automatin'***!