Class: R53z::Client

Inherits:
Object
  • Object
show all
Includes:
Methadone::CLILogging, Methadone::Main
Defined in:
lib/r53z/client.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(section, creds) ⇒ Client

Returns a new instance of Client.



7
8
9
10
11
12
13
# File 'lib/r53z/client.rb', line 7

def initialize(section, creds)
  @client = Aws::Route53::Client.new(
    access_key_id: creds[section]['aws_access_key_id'],
    secret_access_key: creds[section]['aws_secret_access_key'],
    region: creds[section]['region']
  )
end

Instance Attribute Details

#clientObject

Returns the value of attribute client.



5
6
7
# File 'lib/r53z/client.rb', line 5

def client
  @client
end

Instance Method Details

#create(info:, records: nil) ⇒ Object

Create zone with record(s) from an info and records hash



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/r53z/client.rb', line 63

def create(info:, records: nil)
  rv = {} # pile up the responses in a single hash
  #if self.list(name: info[:name]).any?
  #  error(info[:name].to_s + " already exists")
  #end
  # XXX: AWS sends out a data structure with config:, but expects
  # hosted_zone_config: on create/restore. argh.
  # XXX: also, private_zone is not accepted here for some reason
  zone = info[:hosted_zone]
  # Populate a zone_data hash with options for zone creation
  zone_data = {}
  zone_data[:name] = zone[:name]
  zone_data[:caller_reference] = 'r53z-create-' + self.random_string
  if zone[:config] and zone[:config][:comment]
    zone_data[:hosted_zone_config] = {}
    zone_data[:hosted_zone_config][:comment] = zone[:config][:comment]
  end
  if info[:delegation_set] and info[:delegation_set][:id]
    zone_data[:delegation_set_id] = info[:delegation_set][:id]
  end
  zone_resp = self.client.create_hosted_zone(zone_data)
  rv[:hosted_zone_resp] = zone_resp

  rv[:record_set_resp] = []
  # Optionally populate records
  if records
    records.each do |record|
      # skip these, as they are handled separately (delegation sets)
      unless (record[:type] == "NS" || record[:type] == "SOA")
        record_resp = self.client.change_resource_record_sets({
          :hosted_zone_id => zone_resp[:hosted_zone][:id],
          :change_batch => {
            :changes => [
              {
                :action => "CREATE",
                :resource_record_set => record
              }
            ]
          }
        })
        rv[:record_set_resp].push(record_resp)
      end
    end
  end
  return rv
end

#create_delegation_set(zone_id = nil) ⇒ Object

create a new delegation set, optionally associated with an existing zone



199
200
201
202
203
204
# File 'lib/r53z/client.rb', line 199

def create_delegation_set(zone_id = nil)
  self.client.create_reusable_delegation_set({
    caller_reference: 'r53z-create-del-set-' + self.random_string,
    hosted_zone_id: zone_id
  })
end

#delete(name, id: nil) ⇒ Object

delete a zone by name, optionally by ID



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/r53z/client.rb', line 111

def delete(name, id: nil)
  # if we got both, demand nil for name, because AWS allows multiples
  if name and id
    raise "Name should be nil when id is provided."
  end
  unless id
    id = self.list(:name => name).first[:id]
  end
  # Can't delete unless empty of record sets
  self.delete_all_rr_sets(id)
  client.delete_hosted_zone(:id => id)
end

#delete_all_rr_sets(zone_id) ⇒ Object

delete all of the resource record sets in a zone (this is required to delete a zone



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/r53z/client.rb', line 126

def delete_all_rr_sets(zone_id)
  self.list_records(zone_id).reject do |rs|
    (rs[:type] == "NS" || rs[:type] == "SOA")
  end.each do |record_set|
    self.client.change_resource_record_sets({
      :hosted_zone_id => zone_id,
      :change_batch => {
        :changes => [{
          :action=> "DELETE",
          :resource_record_set => record_set
        }]
      }
    })
  end
end

#delete_delegation_set(id: nil, name: nil) ⇒ Object

delete a delegation set by ID or name



220
221
222
223
224
225
226
227
# File 'lib/r53z/client.rb', line 220

def delete_delegation_set(id: nil, name: nil)
  if name and not id
    id = get_delegation_set_id(name)
  end
  self.client.delete_reusable_delegation_set({
    id: id
  })
end

#dump(dirpath, name) ⇒ Object

dump a zone to a direcory. Will generate two files; a zoneinfo file and a records file.



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/r53z/client.rb', line 144

def dump(dirpath, name)
  # Get the ID
  zone_id = self.list(:name => name).first[:id]

  # normalize name
  unless name[-1] == '.'
    name = name + '.'
  end

  # dump the record sets
  R53z::JsonFile.write_json(
    path: File.join(dirpath, name),
    data: self.list_records(zone_id))

  # Dump the zone metadata, plus the delegated set info
  out = { :hosted_zone =>
            self.client.get_hosted_zone({ :id => zone_id}).hosted_zone.to_h,
          :delegation_set =>
            self.client.get_hosted_zone({:id => zone_id}).delegation_set.to_h
        }

  R53z::JsonFile.write_json(
    path: File.join(dirpath, name + "zoneinfo"),
    data: out)
end

#get_delegation_set(id) ⇒ Object

get details of a delegation set specified by ID, incuding name servers



213
214
215
216
217
# File 'lib/r53z/client.rb', line 213

def get_delegation_set(id)
  self.client.get_reusable_delegation_set({
    id: id
  })
end

#get_delegation_set_id(name) ⇒ Object

Get delegation set ID for the given zone



230
231
232
233
234
235
236
237
238
239
# File 'lib/r53z/client.rb', line 230

def get_delegation_set_id(name)
  begin
    zone_id = self.list(:name => name).first[:id]
  rescue
    return nil
  end
  return self.client.get_hosted_zone({
    id: zone_id
  }).delegation_set[:id]
end

#get_zone_id(name) ⇒ Object

Get the zone id from name



251
252
253
# File 'lib/r53z/client.rb', line 251

def get_zone_id(name)
  self.list(:name => name).first[:id]
end

#list(name: nil, delegation_set_id: nil) ⇒ Object

list one or all zones by name and ID XXX Should ideally be a lazy iterator, need to read up on how that’s done in Ruby.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/r53z/client.rb', line 18

def list(name: nil, delegation_set_id: nil)
  rv = []
  if name
    name = name.to_s + "." unless name[-1] == '.'
    zone = self.client.list_hosted_zones_by_name({
      dns_name: name,
      max_items: 1
    })
    # Response will always contain some zone, even if the one
    # we want doesn't exist...so, if no match, return empty set
    if zone.hosted_zones.first and zone.hosted_zones.first.name == name
      rv.push({
        :name => zone.hosted_zones.first.name,
        :id =>   zone.hosted_zones.first.id,
      })
    end
  else
    is_truncated = true
    marker = nil
    # If more than 100, will be divided into sets, so we have to
    # poll multiple times
    while is_truncated
      if marker
        resp = self.client.list_hosted_zones(
          delegation_set_id: delegation_set_id,
          marker: marker,
          )
        # R53 imposes a 5 request per second limit
        # Could catch "throttling"/"rate exceeded" error and retry
        sleep 0.3
      else
        resp = self.client.list_hosted_zones(
          delegation_set_id: delegation_set_id)
      end
      is_truncated = resp['is_truncated']
      marker = resp['next_marker']
      resp['hosted_zones'].each do |z|
        rv.push({:name => z[:name], :id => z[:id]})
      end
    end
  end
  return rv
end

#list_by_id(id) ⇒ Object

Get zone information in a different way for testing



242
243
244
245
246
247
248
# File 'lib/r53z/client.rb', line 242

def list_by_id(id)
  # no begin, let exceptions bubble up, maybe it'll give useful data
  zone = self.client.get_hosted_zone({
    id: id
  }).hosted_zone
  return zone
end

#list_delegation_setsObject

list all delegation sets



207
208
209
210
# File 'lib/r53z/client.rb', line 207

def list_delegation_sets
  resp = self.client.list_reusable_delegation_sets({})
  return resp.delegation_sets
end

#list_records(zone_id) ⇒ Object



189
190
191
192
193
194
195
196
# File 'lib/r53z/client.rb', line 189

def list_records(zone_id)
  records = self.client.list_resource_record_sets(hosted_zone_id: zone_id)
  rv = []
  records[:resource_record_sets].each do |record|
    rv.push(record.to_h)
  end
  rv
end

#random_string(len = 16) ⇒ Object

random string generator helper function



256
257
258
# File 'lib/r53z/client.rb', line 256

def random_string(len=16)
  rand(36**len).to_s(36)
end

#restore(path, domain, delegation = nil) ⇒ Object

Restore a zone from the given path. It expects files named zone.zoneinfo.json and zone.json



172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/r53z/client.rb', line 172

def restore(path, domain, delegation = nil)
  # normalize domain
  unless domain[-1] == '.'
    domain = domain + '.'
  end
  # Load up the zone info file
  file = File.join(path, domain)
  info = R53z::JsonFile.read_json(path: file + "zoneinfo")
  if delegation
    # inject the specified delegation set into info, overriding file
    info[:delegation_set] = {:id => delegation }
  end
  records = R53z::JsonFile.read_json(path: file)
  # create the zone and the record sets
  self.create(:info => info, :records => records)
end