Skip to content

Commit

Permalink
Upgrade to Python 3.x and to Alpine-based Docker image.
Browse files Browse the repository at this point in the history
  • Loading branch information
Arik Kfir committed Nov 28, 2017
1 parent 1f7eb45 commit 4e19d39
Show file tree
Hide file tree
Showing 5 changed files with 64 additions and 63 deletions.
2 changes: 1 addition & 1 deletion .buildkite/release.pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ steps:
- gcloud docker -- pull gcr.io/infolinks-gcr/k8s-cloudflared:${BUILDKITE_COMMIT}
- gcloud docker -- tag gcr.io/infolinks-gcr/k8s-cloudflared:${BUILDKITE_COMMIT} infolinks/k8s-cloudflared:${VERSION}
- gcloud docker -- push infolinks/k8s-cloudflared:${VERSION}
- gcloud docker -- tag gcr.io/infolinks-gcr/k8s-cloudflared:${BUILDKITE_COMMIT} infolinks/k8s-cloudflared:latest
- gcloud docker -- tag infolinks/k8s-cloudflared:${VERSION} infolinks/k8s-cloudflared:latest
- gcloud docker -- push infolinks/k8s-cloudflared:latest
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
.python2.7
/.python3
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# TODO: use alpine variant of cloud-sdk image
FROM google/cloud-sdk:168.0.0
FROM infolinks/cloud-sdk:178.0.0-alpine
MAINTAINER Arik Kfir <arik@infolinks.com>
RUN apt-get update -qqy && apt-get install -qqy jq && rm -rf /var/lib/apt/lists/* && \
pip --quiet --disable-pip-version-check --no-cache-dir install requests
RUN apk --no-cache --update add jq tree bash python3 py3-pip && \
pip3 install requests && \
gcloud components install kubectl
COPY cloudflared.sh update_dns_records.py /usr/local/bin/
RUN chmod a+x /usr/local/bin/cloudflared.sh /usr/local/bin/update_dns_records.py
ENTRYPOINT ["/usr/local/bin/cloudflared.sh"]
2 changes: 1 addition & 1 deletion k8s-cloudflared.iml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Python 2.7 (k8s-cloudflared)" jdkType="Python SDK" />
<orderEntry type="jdk" jdkName="Python 3.6 (k8s-cloudflared)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
113 changes: 57 additions & 56 deletions update_dns_records.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,109 +2,110 @@
import argparse
import json
import sys
from typing import Mapping, Sequence, Any

import requests

# URL constants
# base Cloudflare URL
CF_BASE_URL = "https://api.cloudflare.com/client/v4"
CF_ZONES_URL = CF_BASE_URL + "/zones"
CF_ZONE_RECORDS = CF_BASE_URL + "/zones/%s/dns_records"
CF_ZONE_RECORD_ID = CF_BASE_URL + "/zones/%s/dns_records/%s"


def build_cloudflare_request_headers(auth_email, auth_key):
def build_cloudflare_request_headers(auth_email: str, auth_key: str) -> Mapping[str, str]:
return {
"Content-Type": "application/json",
"X-Auth-Key": auth_key,
"X-Auth-Email": auth_email
}


def update_dns_record(zone_id, auth_email, auth_key, subdomain, domain, ip_address):
full_name = subdomain + '.' + domain
def update_dns_record(zone_id: int, auth_email: str, auth_key: str, subdomain: str, domain: str, ip_address: str):
records_url: str = f"{CF_BASE_URL}/zones/{zone_id}/dns_records"
full_name: str = subdomain + '.' + domain
desired_record: dict = {
'type': 'A',
'name': subdomain,
'content': ip_address,
'ttl': 1,
'proxied': False
}

dns_lookup = requests.get(CF_ZONE_RECORDS % zone_id,
headers=build_cloudflare_request_headers(auth_email, auth_key),
params={'name': full_name}).json()
dns_lookup: dict = requests.get(url=records_url,
headers=build_cloudflare_request_headers(auth_email=auth_email, auth_key=auth_key),
params={'name': full_name}).json()

if 'result' not in dns_lookup or len(dns_lookup['result']) == 0:
print "Creating missing DNS record for domain name '%s' and IP address '%s'..." % (full_name, ip_address)
requests.post(CF_ZONE_RECORDS % zone_id,
headers=build_cloudflare_request_headers(auth_email, auth_key),
json={'type': 'A', 'name': subdomain, 'content': ip_address, 'ttl': 1, 'proxied': False}) \
.raise_for_status()
print(f"Creating missing DNS record: '{full_name}' -> '{ip_address}'")
requests.post(url=records_url,
headers=build_cloudflare_request_headers(auth_email=auth_email, auth_key=auth_key),
json=desired_record).raise_for_status()

elif len(dns_lookup['result']) > 1:
print "Too many DNS records found for domain name '%s'! (replacing all with a new one)" % full_name
print(f"Too many DNS records found for domain name '{full_name}'! (replacing all)", file=sys.stderr)
for rec in dns_lookup['result']:
rec_id = rec['id']
rec_id: str = rec['id']
if not rec_id or len(rec_id) == 0:
raise Exception("empty record ID encountered!")

##########################################################################################
# CAREFUL WHEN FIDDLING HERE!!!!!!
# using a wrong URL here CAN *** DELETE THE WHOLE ZONE *** !!!!!!!!!!!
##########################################################################################
delete_url = CF_ZONE_RECORD_ID % (zone_id, rec_id)
print "Deleting DNS record with ID '%s' (%s) using URL: %s" % (rec_id, delete_url, rec['content'])
requests.delete(delete_url,
headers=build_cloudflare_request_headers(auth_email, auth_key)) \
.raise_for_status()

print "Creating new record for domain name '%s' and IP address '%s'..." % (full_name, ip_address)
requests.post(CF_ZONE_RECORDS % zone_id,
headers=build_cloudflare_request_headers(auth_email, auth_key),
json={'type': 'A', 'name': subdomain, 'content': ip_address, 'ttl': 1, 'proxied': False}) \
.raise_for_status()
delete_url = f"{records_url}/{rec_id}"
print(f"Deleting DNS record with ID '{rec_id}' ({rec['content']}) using: {delete_url}")
# TODO: re-enable DNS record deletion
# requests.delete(url=delete_url,
# headers=build_cloudflare_request_headers(auth_email=auth_email, auth_key=auth_key))\
# .raise_for_status()

# print(f"Creating replacement record: '{full_name}' -> '{ip_address}'")
# requests.post(url=records_url,
# headers=build_cloudflare_request_headers(auth_email=auth_email, auth_key=auth_key),
# json=desired_record).raise_for_status()

else:
rec = dns_lookup['result'][0]
rec_id = rec['id']
rec_ip_address = rec['content']
rec: dict = dns_lookup['result'][0]
rec_id: str = rec['id']
rec_ip_address: str = rec['content']
if rec_ip_address != ip_address:
print "Updating DNS record with domain name '%s' to IP address '%s'..." % (full_name, ip_address)
requests.put(CF_ZONE_RECORD_ID % (zone_id, rec_id),
headers=build_cloudflare_request_headers(auth_email, auth_key),
json={'type': 'A', 'name': subdomain, 'content': ip_address, 'ttl': 1, 'proxied': False}) \
.raise_for_status()
print(f"Updating DNS record '{rec_id}': '{full_name}' -> '{ip_address}'")
requests.put(url=f"{records_url}/{rec_id}",
headers=build_cloudflare_request_headers(auth_email=auth_email, auth_key=auth_key),
json=desired_record).raise_for_status()


def main():
argparser = argparse.ArgumentParser(description='Updates Cloudflare DNS records')
argparser.add_argument('--domain',
metavar='DOMAIN',
required=True,
help='the public suffix domain name, eg. \'mydomain.com\' (which is the default)')
argparser.add_argument('--auth-email',
required=True,
metavar='EMAIL',
help='Email of the account used to connect to Cloudflare')
argparser.add_argument('--auth-key',
required=True,
metavar='KEY',
help='authentication key of the Cloudflare account')
argparser.add_argument('domain', help='public suffix domain name, eg. \'mydomain.com\'')
argparser.add_argument('auth-email', help='Email of the account used to connect to Cloudflare')
argparser.add_argument('auth-key', help='authentication key of the Cloudflare account')
args = argparser.parse_args()

zone_id_ = requests.get(CF_ZONES_URL,
headers=build_cloudflare_request_headers(args.auth_email, args.auth_key),
params={'name': args.domain}).json()['result'][0]['id']
zone: dict = requests.get(
url=f"{CF_BASE_URL}/zones",
headers=build_cloudflare_request_headers(auth_email=args.auth_email, auth_key=args.auth_key),
params={'name': args.domain}).json()['result'][0]

# read JSON from stdin
try:
dns_expected_state = json.loads('\n'.join(sys.stdin.readlines()))
dns_expected_state: Sequence[Mapping[str, Any]] = json.loads('\n'.join(sys.stdin.readlines()))
except:
sys.stderr.write("Failed reading JSON from stdin!\n")
sys.stderr.flush()
raise

# process DNS JSON, updating each individual records for each individual service
for svc in dns_expected_state:
service_domain_names = svc['dns']
service_ip_addresses = svc['ips']
service_domain_names: Sequence[str] = svc['dns']
service_ip_addresses: Sequence[str] = svc['ips']
for dns in service_domain_names:
for ip_address in service_ip_addresses:
subdomain = dns[0:dns.rfind('.' + args.domain)] if dns.endswith('.' + args.domain) else dns
update_dns_record(zone_id_, args.auth_email, args.auth_key, subdomain, args.domain, ip_address)
subdomain: str = dns[0:dns.rfind('.' + args.domain)] if dns.endswith('.' + args.domain) else dns
update_dns_record(zone_id=int(zone['id']),
auth_email=args.auth_email,
auth_key=args.auth_key,
subdomain=subdomain,
domain=args.domain,
ip_address=ip_address)


if __name__ == "__main__":
Expand Down

0 comments on commit 4e19d39

Please sign in to comment.