Skip to content

Instantly share code, notes, and snippets.

@ytti
Created July 8, 2021 15:32
Show Gist options
  • Save ytti/69e14c90ba15c6e86d6c3b6af5ee7475 to your computer and use it in GitHub Desktop.
Save ytti/69e14c90ba15c6e86d6c3b6af5ee7475 to your computer and use it in GitHub Desktop.
CLI UX to Google Cloud DNS
#!/usr/bin/env ruby
require "google/cloud/dns"
require "commander"
#require "pry"
#
class GDNS
PROJECT = "resonant-petal-318815"
AUTH = File.join(Dir.home, ".config", "google_cloud_sdk_auth.json")
TTL = 1800
MX = [
"1 aspmx.l.google.com.",
"5 alt1.aspmx.l.google.com.",
"5 alt2.aspmx.l.google.com.",
"10 alt3.aspmx.l.google.com.",
"10 alt4.aspmx.l.google.com."
]
def initialize
auth
@dns = Google::Cloud::Dns.new
end
def get_zone(domain)
@dns.zone(Domain.new(domain).name)
end
def create_zone(domain)
@dns.create_zone(*Domain.new(domain).both)
end
def delete_zone(domain)
zone = get_zone(domain)
zone.delete(force: true)
end
def list_zones
@dns.zones
end
def export_zone(domain, file)
get_zone(domain).export(file)
end
def import_zone(domain, file)
get_zone(domain).import(file)
end
def create_zone_standard(domain, ip4=nil, ip6=nil)
domain = Domain.new(domain).domain
zone = create_zone(domain)
zone.update do |tx|
tx.add(domain, "A", TTL, ip4) if ip4
tx.add(domain, "AAAA", TTL, ip6) if ip6
tx.add("www", "CNAME", TTL, domain)
tx.add(domain, "MX", TTL, MX)
end
zone
end
def set(domain, key, type, values, replace: false, ttl: TTL)
zone = get_zone(domain)
zone.update do |tx|
if type.downcase == "txt"
values = values.map do |value|
value.chars.each_slice(255).map(&:join).map{|e|"\"#{e}\""}.join(" ")
end
end
cmd = replace ? :replace : :add
tx.send(cmd, key, type.upcase, ttl, values)
end
end
def del(domain, key, type)
zone = get_zone(domain)
zone.update do |tx|
tx.remove(key, type.upcase)
end
end
def dnssec(domain)
zone = get_zone(domain)
zone.gapi.dnssec_config.state = "on"
zone.update do |tx|
# binding.pry
end
end
def auth
Google::Cloud::Dns.configure do |config|
config.project_id = PROJECT
config.credentials = AUTH
end
end
class Domain
attr_reader :domain
def initialize(domain)
@domain = domain
@domain = @domain + "." unless @domain[-1] == "."
end
def name
@domain.tr(".", "-")[0..-2]
end
def both
[name, @domain]
end
end
end
class GNDS_CLI
include Commander::Methods
def run
program :name, "GDNS"
program :version, "1.0.0"
program :description, "Interface with google cloud DNS"
command :set do |c|
c.syntax = "gnds set $domain $key $type $value1 [$value2] |$valueN]"
c.description = "set example.com www A 192.0.2.42 192.0.2.222"
c.option("-r", "--replace", "Replace existing record")
c.action do |args, opt|
raise("need 4 or more arguments") unless args.size >= 4
domain, key, type, *values = args
key = modkey(key, domain)
GDNS.new.set(domain, key, type, values, replace: opt.replace)
end
end
command :get do |c|
c.syntax = "gdns get $domain"
c.description = "get example.com"
c.action do |args, opt|
raise("need exactly 1 argument") unless args.size == 1
print_zone(GDNS.new.get_zone(*args))
end
end
command :del do |c|
c.syntax = "gnds del $domain $key $type"
c.description = "del example.com www A"
c.action do |args, opt|
raise("need exactly 3 arguments") unless args.size == 3
domain, key, type = args
key = modkey(key, domain)
GDNS.new.del(domain, key, type)
end
end
command :create do |c|
c.syntax = "gnds create $domain [$ip4] [$ip6]"
c.description = "create example.com 192.0.2.42 2001:db8::42"
c.action do |args, opt|
raise("need 1-3 argument") unless (1..3) === args.size
print_zone(GDNS.new.create_zone_standard(*args))
end
end
command :destroy do |c|
c.syntax = "gnds destroy $domain"
c.description = "destroy example.com"
c.action do |args, opt|
raise("need exactly 1 argument") unless args.size == 1
GDNS.new.delete_zone(*args)
end
end
command :domains do |c|
c.syntax = "gdns domains"
c.description = "domains"
c.action do
puts GDNS.new.list_zones.map(&:dns)
end
end
command :export do |c|
c.syntax = "gdns export $domain $file"
c.description = "export example.com /tmp/example.zone"
c.action do |args, opt|
raise("need exactly 2 arguments") unless args.size == 2
GDNS.new.export_zone(*args)
end
end
command :import do |c|
c.syntax = "gdns import $domain $file"
c.description = "import example.com /tmp/example.zone"
c.action do |args, opt|
raise("need exactly 2 arguments") unless args.size == 2
GDNS.new.import_zone(*args)
end
end
def print_zone(zone)
indent_name = zone.records.max_by{|r|r.name.size}.name.size
indent_type = zone.records.max_by{|r|r.type.size}.type.size
indent = indent_name+indent_type+2
zone.records.map do |r|
puts"%#{indent_name}s %#{indent_type}s %s\n" % [r.name, r.type, r.data.first]
r.data[1..-1].each { |data| puts " "*indent + data }
end
end
def modkey(key, domain)
domain = GDNS::Domain.new(domain).domain
key == "." ? domain : key
end
run!
end
end
begin
if __FILE__ == $0
GNDS_CLI.new.run
end
rescue => error
warn error
#raise error
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment