Class: EZDyn::Client
- Inherits:
-
Object
- Object
- EZDyn::Client
- Defined in:
- lib/ezdyn/client.rb,
lib/ezdyn/crud.rb,
lib/ezdyn/zone.rb,
lib/ezdyn/changes.rb
Overview
The main class for Dyn REST API interaction.
For more information about the API, see / the official documentation.
Instance Method Summary collapse
- #add_pending_change(chg) ⇒ Object
- #build_uri(type:, fqdn:, id: nil) ⇒ Object
- #build_url(path) ⇒ Object
-
#call_api(method: "get", uri:, payload: {}, max_attempts: EZDyn::API_RETRY_MAX_ATTEMPTS) ⇒ Response
Performs an actual REST call.
- #clear_pending_changes(zone: nil) ⇒ Object
-
#commit(zone: nil, message: nil) ⇒ Object
Commits any pending changes to a zone or to all zones with an optional update message.
-
#create(type:, fqdn:, value:, ttl: nil) ⇒ Record
Calls the Dyn API to create a new record.
-
#delete(record:) ⇒ Object
Delete a record.
-
#delete_all(type: :any, fqdn:) ⇒ Object
Deletes all records of a specified type and FQDN.
-
#exists?(type: :any, fqdn:, id: nil) ⇒ Boolean
Signals if a particular record exists.
-
#fetch_record(type:, fqdn:, id:) ⇒ EZDyn::Record
Fetches a record if you know the type, FQDN, and ID.
- #fetch_uri_data(uri:) ⇒ Object
- #fetch_zones ⇒ Object
-
#guess_zone(fqdn:) ⇒ Zone
Match the given FQDN to a zone known to this client.
-
#initialize(customer_name: ENV['DYN_CUSTOMER_NAME'], username: ENV['DYN_USERNAME'], password: ENV['DYN_PASSWORD']) ⇒ Client
constructor
Initializes a new Dyn REST API client.
-
#logged_in? ⇒ Boolean
Signals whether the client has successfully logged in.
-
#login ⇒ Object
Begin a Dyn REST API session.
-
#logout ⇒ Object
End the current Dyn REST API session.
- #pending_change_zones ⇒ Object
-
#pending_changes(zone: nil) ⇒ Array
List currently pending changes (optionally per zone).
-
#records_for(type: :any, fqdn:) ⇒ Array<EZDyn::Record>
Fetches all records for a particular FQDN (and record type).
-
#records_in_zone(zone:) ⇒ Object
Fetches all records for a zone.
-
#rollback(zone: nil) ⇒ Object
Rolls back any pending changes to a zone or to all zones.
-
#update(record: nil, type: nil, fqdn: nil, value: nil, ttl: nil) ⇒ Record
Calls the Dyn API to update or create a record.
-
#zones ⇒ Array<Zone>
List all DNS zones known to this client.
Constructor Details
#initialize(customer_name: ENV['DYN_CUSTOMER_NAME'], username: ENV['DYN_USERNAME'], password: ENV['DYN_PASSWORD']) ⇒ Client
Initializes a new Dyn REST API client.
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/ezdyn/client.rb', line 16 def initialize(customer_name: ENV['DYN_CUSTOMER_NAME'], username: ENV['DYN_USERNAME'], password: ENV['DYN_PASSWORD']) @customer_name = customer_name @username = username @password = password if @customer_name.nil? or @username.nil? or @password.nil? EZDyn.info { "Some credentials are missing" } raise "Missing credentials" end @base_url = URI('https://api2.dynect.net/') @headers = { 'Content-Type' => 'application/json', } @logged_in = false end |
Instance Method Details
#add_pending_change(chg) ⇒ Object
4 5 6 7 |
# File 'lib/ezdyn/changes.rb', line 4 def add_pending_change(chg) @pending_changes ||= [] @pending_changes << chg end |
#build_uri(type:, fqdn:, id: nil) ⇒ Object
139 140 141 142 |
# File 'lib/ezdyn/client.rb', line 139 def build_uri(type:, fqdn:, id: nil) EZDyn.debug { "Client.build_uri( type: #{type}, fqdn: #{fqdn}, id: #{id} )" } "#{RecordType.find(type).uri_name}/#{self.guess_zone(fqdn: fqdn)}/#{fqdn}/#{id}" end |
#build_url(path) ⇒ Object
35 36 37 38 39 40 |
# File 'lib/ezdyn/client.rb', line 35 def build_url(path) path = path.to_s path = "/#{path}" unless path.start_with?('/') path = "/REST#{path}" unless path.start_with?('/REST') @base_url.merge(path) end |
#call_api(method: "get", uri:, payload: {}, max_attempts: EZDyn::API_RETRY_MAX_ATTEMPTS) ⇒ Response
Performs an actual REST call.
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 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 |
# File 'lib/ezdyn/client.rb', line 48 def call_api(method: "get", uri:, payload: {}, max_attempts: EZDyn::API_RETRY_MAX_ATTEMPTS) EZDyn.debug { "API CALL: #{method.to_s.upcase} #{uri} #{ ( payload || {} ).to_json }" } self.login if not self.logged_in? and uri != "Session" payload_str = payload.to_json if payload == {} payload_str = nil end response = nil begin EZDyn.debug { "About to make REST request to #{method.to_s.upcase} #{uri}" } request_url = build_url(uri) request = Net::HTTP.const_get(method.capitalize).new(request_url) @headers.each do |name, value| request[name] = value end request.body = payload_str if payload_str http = Net::HTTP.new(request_url.host, request_url.port) http.use_ssl = true if request_url.scheme == 'https' response = Response.new(http.start { |http| http.request(request) }) if response.delayed? wait = EZDyn::API_RETRY_DELAY_SECONDS max_attempts.times do |retry_count| EZDyn.debug { "Async API call response retrieval, attempt #{retry_count + 1}" } EZDyn.debug { "Waiting for #{wait} seconds" } sleep wait response = self.call_api(uri: "/REST/Job/#{response.job_id}") break if response.success? EZDyn.debug { "Async response status: #{response.status}" } wait += (retry_count * EZDyn::API_RETRY_BACKOFF) end end EZDyn.debug { "Call was successful" } rescue => e EZDyn.info { "REST request to #{uri} threw an exception: #{e}" } raise "Got an exception: #{e}" end response end |
#clear_pending_changes(zone: nil) ⇒ Object
31 32 33 34 35 36 37 38 39 |
# File 'lib/ezdyn/changes.rb', line 31 def clear_pending_changes(zone: nil) if zone.nil? @pending_changes = [] else @pending_changes.delete_if do |pc| pc.zone.name == zone.to_s end end end |
#commit(zone: nil, message: nil) ⇒ Object
Commits any pending changes to a zone or to all zones with an optional update message.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 |
# File 'lib/ezdyn/crud.rb', line 165 def commit(zone: nil, message: nil) EZDyn.debug { "Client{}.commit( zone: #{zone} )" } payload = { publish: true } payload[:notes] = if not .nil? zones = zone.nil? ? self.pending_change_zones : [Zone.find(zone)] zones.each do |zone| EZDyn.debug { " - committing Zone{#{zone.name}}" } response = self.call_api( method: "put", uri: "Zone/#{zone}", payload: payload ) if response.success? self.clear_pending_changes(zone: zone) else EZDyn.debug { " - failed to commit Zone{#{zone.name}}: #{response.}" } raise "Could not commit zone #{zone.name}: #{response.}" end end end |
#create(type:, fqdn:, value:, ttl: nil) ⇒ Record
As a side effect upon success, this method creates a [CreateChange] object in the ‘pending_changes` array.
Calls the Dyn API to create a new record.
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/ezdyn/crud.rb', line 14 def create(type:, fqdn:, value:, ttl: nil) EZDyn.debug { "Client.create( type: #{type}, fqdn: #{fqdn}, value: #{value}, ttl: #{ttl} )" } ttl = ( ttl || Record::DefaultTTL ).to_i values = Array(value) return values.map do |val| value_key = Array(RecordType.find(type).value_key) split_val = val.split(' ', value_key.length) response = self.call_api( method: "post", uri: self.build_uri(type: type, fqdn: fqdn), payload: { rdata: value_key.zip(split_val).to_h, ttl: ttl } ) if not response.success? raise "Failed to create record: #{response.}" end record = Record.new(client: self, raw: response.data) self.add_pending_change(CreateChange.new(record: record)) record end end |
#delete(record:) ⇒ Object
As a side effect upon success, this method creates a [DeleteChange] object in the ‘pending_changes` array.
Delete a record.
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/ezdyn/crud.rb', line 103 def delete(record:) EZDyn.debug { "Client{}.delete( record: Record{#{record.record_id}} )" } if not record.sync!.exists? raise "Nothing to delete" end response = self.call_api( method: "delete", uri: record.uri ) if not response.success? raise "Could not delete: #{response.}" end self.add_pending_change(DeleteChange.new(record: record)) end |
#delete_all(type: :any, fqdn:) ⇒ Object
As a side effect upon success, this method creates [DeleteChange] objects in the ‘pending_changes` array for each record deleted.
Deletes all records of a specified type and FQDN. Specify type ‘:any` (or don’t specify ‘type` at all) to delete all records for a FQDN.
130 131 132 133 134 135 |
# File 'lib/ezdyn/crud.rb', line 130 def delete_all(type: :any, fqdn:) EZDyn.debug { "Client{}.delete_all( type: #{type}, fqdn: #{fqdn} )" } self.records_for(type: type, fqdn: fqdn).each do |record| record.delete! end end |
#exists?(type: :any, fqdn:, id: nil) ⇒ Boolean
Signals if a particular record exists.
201 202 203 204 205 206 207 208 209 210 |
# File 'lib/ezdyn/client.rb', line 201 def exists?(type: :any, fqdn:, id: nil) EZDyn.debug { "Client.exists?( type: #{type}, fqdn: #{fqdn}, id: #{id} )" } if not id.nil? and type != :any EZDyn.debug { "Fetching a single record" } self.fetch_record(type: type, fqdn: fqdn, id: id) != [] else EZDyn.debug { "Fetching records_for #{type} #{fqdn}" } self.records_for(type: type, fqdn: fqdn).count > 0 end end |
#fetch_record(type:, fqdn:, id:) ⇒ EZDyn::Record
Fetches a record if you know the type, FQDN, and ID.
163 164 165 166 167 168 169 170 171 |
# File 'lib/ezdyn/client.rb', line 163 def fetch_record(type:, fqdn:, id:) EZDyn.debug { "Client.fetch_record( type: #{type}, fqdn: #{fqdn}, id: #{id} )" } data = self.fetch_uri_data(uri: self.build_uri(type: type, fqdn: fqdn, id: id)) if data == [] nil else Record.new(client: self, raw: data) end end |
#fetch_uri_data(uri:) ⇒ Object
145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/ezdyn/client.rb', line 145 def fetch_uri_data(uri:) EZDyn.debug { "Client.fetch_uri_data( uri: #{uri} )" } response = call_api(uri: uri) if response.success? EZDyn.debug { "fetch_uri_data success!" } return response.data else EZDyn.debug { "fetch_uri_data failure!" } return [] end end |
#fetch_zones ⇒ Object
91 92 93 94 95 |
# File 'lib/ezdyn/zone.rb', line 91 def fetch_zones EZDyn.debug { "Client.fetch_zones()" } @zones = self.fetch_uri_data(uri: '/Zone/'). collect { |uri| Zone.new(client: self, uri: uri) } end |
#guess_zone(fqdn:) ⇒ Zone
Match the given FQDN to a zone known to this client.
108 109 110 111 |
# File 'lib/ezdyn/zone.rb', line 108 def guess_zone(fqdn:) EZDyn.debug { "Client.guess_zone( fqdn: #{fqdn} )" } self.zones.find { |z| fqdn.downcase =~ /#{z.name.downcase}$/ } end |
#logged_in? ⇒ Boolean
Signals whether the client has successfully logged in.
100 101 102 |
# File 'lib/ezdyn/client.rb', line 100 def logged_in? @logged_in end |
#login ⇒ Object
Begin a Dyn REST API session. This method will be called implicitly when required.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
# File 'lib/ezdyn/client.rb', line 105 def login EZDyn.debug { "Logging in..." } response = call_api( method: "post", uri: "Session", payload: { customer_name: @customer_name, user_name: @username, password: @password, } ) EZDyn.debug { "Response status: #{response.status}" } if response.success? @headers['Auth-Token'] = response.data["token"] @logged_in = true else raise "Login failed" end end |
#logout ⇒ Object
End the current Dyn REST API session.
128 129 130 131 132 133 134 135 136 |
# File 'lib/ezdyn/client.rb', line 128 def logout call_api( method: "delete", uri: "Session" ) @headers.delete('Auth-Token') @logged_in = false end |
#pending_change_zones ⇒ Object
10 11 12 |
# File 'lib/ezdyn/changes.rb', line 10 def pending_change_zones @pending_changes.collect(&:zone).uniq end |
#pending_changes(zone: nil) ⇒ Array
List currently pending changes (optionally per zone).
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/ezdyn/changes.rb', line 19 def pending_changes(zone: nil) if zone.nil? @pending_changes else zone = Zone.new(client: self, name: zone) @pending_changes.select do |pc| pc.zone.name == zone.name end end end |
#records_for(type: :any, fqdn:) ⇒ Array<EZDyn::Record>
Fetches all records for a particular FQDN (and record type).
179 180 181 182 183 |
# File 'lib/ezdyn/client.rb', line 179 def records_for(type: :any, fqdn:) EZDyn.debug { "Client.records_for( type: #{type}, fqdn: #{fqdn} )" } self.fetch_uri_data(uri: self.build_uri(type: type, fqdn: fqdn)). collect { |uri| Record.new(client: self, uri: uri) } end |
#records_in_zone(zone:) ⇒ Object
Fetches all records for a zone. NOTE: This can take very long, and probably isn’t what you want.
188 189 190 191 192 |
# File 'lib/ezdyn/client.rb', line 188 def records_in_zone(zone:) EZDyn.debug { "Client.records_in_zone( zone: #{zone}" } self.fetch_uri_data(uri: "/AllRecord/#{zone}"). collect{ |uri| Record.new(client: self, uri: uri) } end |
#rollback(zone: nil) ⇒ Object
Rolls back any pending changes to a zone or to all zones.
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/ezdyn/crud.rb', line 141 def rollback(zone: nil) EZDyn.debug { "Client{}.rollback( zone: #{zone} )" } zones = zone.nil? ? self.pending_change_zones : [Zone.new(client: self, name: zone)] zones.each do |zone| EZDyn.debug { " - rolling back Zone{#{zone.name}}" } response = self.call_api(method: "delete", uri: "ZoneChanges/#{zone}") if response.success? self.clear_pending_changes(zone: zone) else EZDyn.debug { " - failed to roll back Zone{#{zone.name}}: #{response.}" } raise "Failed to roll back zone #{zone.name}" end end end |
#update(record: nil, type: nil, fqdn: nil, value: nil, ttl: nil) ⇒ Record
As a side effect upon success, this method creates an [UpdateChange] or a [CreateChange] object in the ‘pending_changes` array.
Calls the Dyn API to update or create a record. Could also be called ‘upsert`.
54 55 56 57 58 59 60 61 62 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 |
# File 'lib/ezdyn/crud.rb', line 54 def update(record: nil, type: nil, fqdn: nil, value: nil, ttl: nil) EZDyn.debug { "Client.update( record: #{record.nil? ? nil : "Record{#{record.record_id}}"}, type: #{type}, fqdn: #{fqdn}, value: #{value}, ttl: #{ttl} )" } values = Array(value) if record.nil? if type.nil? or fqdn.nil? raise "Cannot update a record without a Record object or both record type and FQDN" end records = self.records_for(type: type, fqdn: fqdn) else records = [record] end response = nil if records.count.zero? && (fqdn.nil? || type.nil? || value.nil?) raise "Record doesn't exist, and insufficient information to create it was given" end response = self.call_api( method: "put", uri: self.build_uri(type: type, fqdn: fqdn), payload: { "#{type}Records" => values.map do |val| value_key = Array(RecordType.find(type).value_key) split_val = val.split(' ', value_key.length) { rdata: value_key.zip(split_val).to_h, ttl: ttl || records.map(&:ttl).min } end } ) if not response.success? raise "Could not update: #{response.}" end new_records = response.data.map { |res| Record.new(client: self, raw: res) } self.add_pending_change(UpdateChange.new(records: records, new_records: new_records)) return new_records end |