Class: Porkbun::Record

Inherits:
Abstract show all
Defined in:
lib/porkbun.rb

Instance Attribute Summary collapse

Attributes inherited from Abstract

#message, #status

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Abstract

#parse_response, #success?

Constructor Details

#initialize(options) ⇒ Record

Returns a new instance of Record.



59
60
61
62
63
64
65
66
67
# File 'lib/porkbun.rb', line 59

def initialize(options)
  @name = options[:name]
  @content = options[:content]
  @type = options[:type]
  @ttl = options[:ttl] || 600
  @prio = options[:prio]
  @domain = options[:domain]
  @id = options[:id]
end

Instance Attribute Details

#content ⇒ Object

Returns the value of attribute content.



57
58
59
# File 'lib/porkbun.rb', line 57

def content
  @content
end

#domain ⇒ Object

Returns the value of attribute domain.



57
58
59
# File 'lib/porkbun.rb', line 57

def domain
  @domain
end

#id ⇒ Object

Returns the value of attribute id.



57
58
59
# File 'lib/porkbun.rb', line 57

def id
  @id
end

#name ⇒ Object

Returns the value of attribute name.



57
58
59
# File 'lib/porkbun.rb', line 57

def name
  @name
end

#notes ⇒ Object

Returns the value of attribute notes.



57
58
59
# File 'lib/porkbun.rb', line 57

def notes
  @notes
end

#prio ⇒ Object

Returns the value of attribute prio.



57
58
59
# File 'lib/porkbun.rb', line 57

def prio
  @prio
end

#ttl ⇒ Object

Returns the value of attribute ttl.



57
58
59
# File 'lib/porkbun.rb', line 57

def ttl
  @ttl
end

#type ⇒ Object

Returns the value of attribute type.



57
58
59
# File 'lib/porkbun.rb', line 57

def type
  @type
end

Class Method Details

.create(options) ⇒ Object



69
70
71
72
# File 'lib/porkbun.rb', line 69

def self.create(options)
  record = Record.new options
  record.create
end

.create_record(record, options) ⇒ Object



131
132
133
134
135
136
# File 'lib/porkbun.rb', line 131

def self.create_record(record, options)
  fqdn = record.chomp('.')
  domain = domain_for(fqdn) || fqdn.split('.').last(2).join('.')
  name = fqdn == domain ? '' : fqdn.delete_suffix(".#{domain}")
  create(options.merge(domain:, name:))
end

.delete_all(domain, id = '') ⇒ Object



152
153
154
# File 'lib/porkbun.rb', line 152

def self.delete_all(domain, id = '')
  list(domain, id).reject { |record| record.type == 'NS' }.each(&:delete)
end

.delete_record(hostname) ⇒ Object



146
147
148
149
150
# File 'lib/porkbun.rb', line 146

def self.delete_record(hostname)
  record = find_record(hostname)
  record.delete
  record
end

.domain_for(hostname) ⇒ Object



105
106
107
108
109
110
111
# File 'lib/porkbun.rb', line 105

def self.domain_for(hostname)
  fqdn = hostname.to_s.chomp('.')
  domains = Porkbun::Domain.list_all[:domains].map { |item| item[:domain] }
  domains.sort_by(&:length).reverse.find do |domain|
    fqdn == domain || fqdn.end_with?(".#{domain}")
  end
end

.find_record(hostname) ⇒ Object

Raises:



123
124
125
126
127
128
129
# File 'lib/porkbun.rb', line 123

def self.find_record(hostname)
  records = records_for(hostname)
  raise Error, 'No record found for hostname' if records.empty?
  raise Error, 'Multiple records found for hostname' if records.length > 1

  records.first
end

.import_zone(file) ⇒ Object



156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/porkbun.rb', line 156

def self.import_zone(file)
  record_regex = /^(?<hostname>[^\s]+)\.\s+(?<ttl>\d+)\s+IN\s+(?<type>[^\s]+)\s*(?<priority>\d+)?\s+(?<content>.+)$/
  IO.readlines(file).filter_map do |line|
    match_data = line.match(record_regex)
    next unless match_data

    labels = match_data[:hostname].split('.')
    options = {
      domain: labels[-2..].join('.'),
      name: labels[0..-3].join('.'),
      ttl: match_data[:ttl],
      type: match_data[:type],
      prio: match_data[:priority],
      content: match_data[:content].chomp('.').gsub(/^"|"$/, '')
    }.compact
    create(options)
  end
end

.list(domain, id = nil) ⇒ Object

Raises:



94
95
96
97
98
99
100
101
102
103
# File 'lib/porkbun.rb', line 94

def self.list(domain, id = nil)
  raise Error, 'need domain' unless domain

  res = Porkbun.porkbun File.join('dns/retrieve', domain, id || '').chomp('/')
  return Error.new(res[:message]) if res[:status] == 'ERROR'

  res[:records].map do |record|
    Record.new record.merge(domain:)
  end
end

.public_ip ⇒ Object



175
176
177
# File 'lib/porkbun.rb', line 175

def self.public_ip
  Net::HTTP.get(URI('https://canhazip.com')).chomp
end

.record_hostname(record, domain) ⇒ Object



189
190
191
192
193
194
# File 'lib/porkbun.rb', line 189

def self.record_hostname(record, domain)
  name = record.name.to_s.chomp('.')
  return domain if name.empty? || name == '@'

  name.end_with?(".#{domain}") ? name : "#{name}.#{domain}"
end

.records_for(target, id = nil) ⇒ Object



113
114
115
116
117
118
119
120
121
# File 'lib/porkbun.rb', line 113

def self.records_for(target, id = nil)
  requested = target.to_s.chomp('.')
  owner = domain_for(requested) || requested.split('.').last(2).join('.')
  records = list(owner, id)
  raise records if records.is_a?(Error)
  return records if owner == requested

  records.select { |record| record_hostname(record, owner) == requested }
end

.relative_record_name(record) ⇒ Object



196
197
198
199
200
201
202
203
# File 'lib/porkbun.rb', line 196

def self.relative_record_name(record)
  name = record.name.to_s.chomp('.')
  domain = record.domain.to_s.chomp('.')
  return '' if name == domain
  return name.delete_suffix(".#{domain}") if name.end_with?(".#{domain}")

  name
end

.update_dynamic(hostname, ip) ⇒ Object



179
180
181
182
183
184
185
186
187
# File 'lib/porkbun.rb', line 179

def self.update_dynamic(hostname, ip)
  records = records_for(hostname)
  return { created: true, record: create_record(hostname, type: 'A', content: ip) } if records.empty?

  record = records.first
  display = record.to_s
  update_record(record, content: ip)
  { created: false, display:, record: }
end

.update_record(record, options) ⇒ Object



138
139
140
141
142
143
144
# File 'lib/porkbun.rb', line 138

def self.update_record(record, options)
  record.name = relative_record_name(record)
  record.content = options[:content] if options[:content]
  record.ttl = options[:ttl] if options[:ttl]
  record.save
  record
end

Instance Method Details

#create ⇒ Object



240
241
242
243
244
245
# File 'lib/porkbun.rb', line 240

def create
  res = Porkbun.porkbun File.join('dns/create', domain), get_options
  parse_response res
  @id = res[:id]
  self
end

#delete ⇒ Object

Raises:



205
206
207
208
209
210
211
# File 'lib/porkbun.rb', line 205

def delete
  raise Error, 'Need ID to delete record' unless id

  res = Porkbun.porkbun File.join('dns/delete', domain, id)
  parse_response res
  self
end

#edit ⇒ Object

Raises:



74
75
76
77
78
79
80
81
# File 'lib/porkbun.rb', line 74

def edit
  raise Error, 'Need ID to id record' unless id

  res = Porkbun.porkbun File.join('dns/edit', domain, id), get_options
  parse_response res
  @id = res[:id]
  self
end

#save ⇒ Object



83
84
85
# File 'lib/porkbun.rb', line 83

def save
  edit
end

#to_h ⇒ Object



228
229
230
231
232
233
234
235
236
237
238
# File 'lib/porkbun.rb', line 228

def to_h
  {
    name: name,
    content: content,
    type: type,
    ttl: ttl,
    prio: prio,
    domain: domain,
    id: id
  }
end

#to_s ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/porkbun.rb', line 213

def to_s
  content_str = case type
                when /TXT|SPF/
                  "\"#{content}\""
                when /MX|CNAME|NS/
                  "#{content}."
                else
                  String(content)
                end

  prio_str = prio == '0' ? '' : prio
  "#{name}. #{ttl} IN #{type} #{prio_str} #{content_str}".tr_s(' ', ' ')
end

#update(options = {}) ⇒ Object



87
88
89
90
91
92
# File 'lib/porkbun.rb', line 87

def update(options = {})
  self.name = Record.relative_record_name(self)
  self.content = options[:content] if options.key?(:content)
  self.ttl = options[:ttl] if options.key?(:ttl)
  save
end