dnsmadeeasy 0.3.5 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +31 -0
  3. data/.gitignore +2 -0
  4. data/.rubocop.yml +6 -0
  5. data/.rubocop_todo.yml +272 -107
  6. data/.travis.yml +2 -1
  7. data/CLAUDE.md +67 -0
  8. data/Gemfile +9 -1
  9. data/README.md +506 -310
  10. data/Rakefile +4 -4
  11. data/dnsmadeeasy.gemspec +44 -27
  12. data/docs/badges/coverage_badge.svg +21 -0
  13. data/docs/dry-cli-based-tools.webloc +8 -0
  14. data/docs/plan-zone-management.md +756 -0
  15. data/docs/plans/01-plan-ruby4-baseline.md +38 -0
  16. data/docs/plans/02-plan-dry-cli-launcher.md +44 -0
  17. data/docs/plans/03-plan-account-command.md +43 -0
  18. data/docs/plans/04-plan-zone-record-model.md +48 -0
  19. data/docs/plans/05-plan-zone-parser.md +41 -0
  20. data/docs/plans/06-plan-zone-formatter.md +43 -0
  21. data/docs/plans/07-plan-zone-export.md +58 -0
  22. data/docs/plans/08-plan-zone-diff.md +42 -0
  23. data/docs/plans/09-plan-zone-apply.md +69 -0
  24. data/docs/plans/10-plan-docs-cleanup.md +55 -0
  25. data/docs/spec-zone-management.md +242 -0
  26. data/exe/dme +13 -2
  27. data/exe/dmez +6 -0
  28. data/lib/dme.rb +6 -6
  29. data/lib/dnsmadeeasy/api/client.rb +31 -32
  30. data/lib/dnsmadeeasy/cli/box_output.rb +9 -0
  31. data/lib/dnsmadeeasy/cli/commands/account.rb +222 -0
  32. data/lib/dnsmadeeasy/cli/commands/base.rb +119 -0
  33. data/lib/dnsmadeeasy/cli/commands/legacy_operation.rb +30 -0
  34. data/lib/dnsmadeeasy/cli/commands/version.rb +22 -0
  35. data/lib/dnsmadeeasy/cli/commands/zone.rb +326 -0
  36. data/lib/dnsmadeeasy/cli/commands.rb +19 -0
  37. data/lib/dnsmadeeasy/cli/input.rb +14 -0
  38. data/lib/dnsmadeeasy/cli/launcher.rb +53 -0
  39. data/lib/dnsmadeeasy/cli/message_helpers.rb +81 -0
  40. data/lib/dnsmadeeasy/cli/reported_error.rb +10 -0
  41. data/lib/dnsmadeeasy/credentials/api_keys.rb +11 -9
  42. data/lib/dnsmadeeasy/credentials/yaml_file.rb +28 -27
  43. data/lib/dnsmadeeasy/credentials.rb +3 -4
  44. data/lib/dnsmadeeasy/runner.rb +102 -98
  45. data/lib/dnsmadeeasy/types.rb +19 -0
  46. data/lib/dnsmadeeasy/version.rb +2 -1
  47. data/lib/dnsmadeeasy/zone/aname_flattener.rb +63 -0
  48. data/lib/dnsmadeeasy/zone/apply_executor.rb +189 -0
  49. data/lib/dnsmadeeasy/zone/apply_result.rb +22 -0
  50. data/lib/dnsmadeeasy/zone/diff.rb +152 -0
  51. data/lib/dnsmadeeasy/zone/file.rb +26 -0
  52. data/lib/dnsmadeeasy/zone/parser.rb +172 -0
  53. data/lib/dnsmadeeasy/zone/plan.rb +28 -0
  54. data/lib/dnsmadeeasy/zone/plan_action.rb +29 -0
  55. data/lib/dnsmadeeasy/zone/plan_renderer.rb +91 -0
  56. data/lib/dnsmadeeasy/zone/provider_record.rb +18 -0
  57. data/lib/dnsmadeeasy/zone/record.rb +44 -0
  58. data/lib/dnsmadeeasy/zone/record_set.rb +23 -0
  59. data/lib/dnsmadeeasy/zone/remote_adapter.rb +94 -0
  60. data/lib/dnsmadeeasy/zone/remote_records.rb +26 -0
  61. data/lib/dnsmadeeasy/zone/serializer.rb +115 -0
  62. data/lib/dnsmadeeasy.rb +61 -31
  63. metadata +184 -23
data/README.md CHANGED
@@ -1,105 +1,420 @@
1
- [![Gem Version](https://badge.fury.io/rb/dnsmadeeasy.svg)](https://badge.fury.io/rb/dnsmadeeasy)
2
- [![Build Status](https://travis-ci.org/kigster/dnsmadeeasy.svg?branch=master)](https://travis-ci.org/kigster/dnsmadeeasy)
3
- [![Maintainability](https://api.codeclimate.com/v1/badges/7a48648b482b5a5c9257/maintainability)](https://codeclimate.com/github/kigster/dnsmadeeasy/maintainability)
4
- [![Test Coverage](https://api.codeclimate.com/v1/badges/7a48648b482b5a5c9257/test_coverage)](https://codeclimate.com/github/kigster/dnsmadeeasy/test_coverage)
1
+ # DnsMadeEasy (`dmez`)
5
2
 
3
+ [![Gem Version](https://badge.fury.io/rb/dnsmadeeasy.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/dnsmadeeasy) ![coverage](docs/badges/coverage_badge.svg) ![Gem Total Downloads](https://img.shields.io/gem/dt/dnsmadeeasy?style=for-the-badge&logoSize=auto)
6
4
 
7
- # DnsMadeEasy — Ruby Client API (Supporting SDK V2.0)
8
5
 
9
- This is a fully featured REST API client for DnsMadeEasy provider. DME is an **excellent** provider, and is highly recommended for their ease of use, very solid API, and great customer support. They also offer free DNS failover with business accounts, which is highly recommended for the arrays of load balancers in front of your app.
6
+ ## Ruby Client API Library Supporting Rest API SDK V2.0
10
7
 
11
- ## Usage
8
+ ### Gem Version 1.0 Supporting Zone Exports, Formatting, Plan, Apply
12
9
 
13
- **DnsMadeEasy** allows you to fetch, create, update DNS records, as long as you know your API key and the secret.
14
10
 
15
- ### Setting up Credentials
16
11
 
17
- You can find your API Key and Secret on the [Account Settings Page](https://cp.dnsmadeeasy.com/account/info) of their UI.
12
+ > [!IMPORTANT]
13
+ >
14
+ > This gem is not backwards compatible with the previous version. It has been updated to work with DNS zone files and follow Terraform's pattern of the `read` → `plan` → `apply` loop. The old `dme` executable is deprecated in favor of `dmez`.
15
+
16
+ ------
17
+
18
+ This gem ships two things:
19
+
20
+ 1. The **`dmez` command line tool** — manage your DNS Made Easy zones as standard zone files with a Terraform-style `export` → `plan` → `apply` workflow, plus direct access to every account API operation.
21
+ 1. A **fully featured Ruby client** for the DNS Made Easy REST API V2.0.
22
+
23
+ DME is an **excellent** provider, and is highly recommended for their ease of use, very solid API, and great customer support. They also offer free DNS failover with business accounts, which is highly recommended for the arrays of load balancers in front of your app.
24
+
25
+ ## Installation
26
+
27
+ Add this line to your application's Gemfile:
28
+
29
+ ```ruby
30
+ gem 'dnsmadeeasy'
31
+ ```
32
+
33
+ And then execute `bundle`, or install it yourself:
34
+
35
+ ```console
36
+ gem install dnsmadeeasy
37
+ ```
38
+
39
+ ## The `dmez` CLI
40
+
41
+ ```bash
42
+ $ dmez --help
43
+ Commands:
44
+ dmez account [ARGUMENT|SUBCOMMAND] # Execute DNS Made Easy account API operation
45
+ dmez version # Print version
46
+ dmez zone [SUBCOMMAND] # Zone file commands
47
+ ```
48
+
49
+ Every command exits `0` on success and `1` on failure, and **stdout carries only payload** (zone files, plan output) — all status boxes, warnings, and progress go to stderr. Piping and redirection are always safe, and every successful operation ends with a green summary box on stderr telling you what was done.
50
+
51
+ ### CLI Credentials
52
+
53
+ You can find your API Key and Secret on the [Account Settings Page](https://cp.dnsmadeeasy.com/account/info) of the DME UI. The CLI resolves credentials in this order:
54
+
55
+ 1. Explicit flags: `--api-key=KEY --api-secret=SECRET`
56
+ 1. An INI file passed via `--credentials=PATH`
57
+ 1. Environment variables `DNSMADEEASY_API_KEY` and `DNSMADEEASY_API_SECRET`
58
+ 1. A default INI file: `~/.dnsmadeeasy/credentials.ini` (or `./.dnsmadeeasy/credential.ini`)
59
+
60
+ The INI format is two lines:
61
+
62
+ ```ini
63
+ dns_dnsmadeeasy_api_key = 06cb6d10-63ca-013f-380b-66219a96f4e3
64
+ dns_dnsmadeeasy_secret_key = 14b9eb80-63ca-013f-380c-66219a96f4e3
65
+ ```
66
+
67
+ (The Ruby API additionally supports multi-account YAML files with optional encryption — see [Ruby API](#ruby-api) below.)
68
+
69
+ ### Zone Management: `dmez zone`
70
+
71
+ This is the flagship feature of version 1.0: your DME zones become plain text files you can diff, review, version-control, and apply — the same `read` → `plan` → `apply` loop you know from Terraform.
72
+
73
+ ```bash
74
+ $ dmez zone --help
75
+ Commands:
76
+ dmez zone apply FILE # Apply DNS changes for a zone file
77
+ dmez zone export DOMAIN # Export DNS Made Easy records as a canonical zone file
78
+ dmez zone fmt FILE # Format a DNS zone file
79
+ dmez zone plan FILE # Plan DNS changes for a zone file
80
+ dmez zone validate FILE # Validate a DNS zone file
81
+ ```
82
+
83
+ #### `dmez zone export`
84
+
85
+ Exports the live records of a domain as a canonical, normalized zone file:
86
+
87
+ ```bash
88
+ dmez zone export example.com --output=example.com.zone
89
+ ```
90
+
91
+ A real-world export looks like this:
92
+
93
+ ```dns
94
+ $ORIGIN example.com.
95
+ $TTL 60
96
+
97
+ @ 300 IN A 151.101.3.52
98
+ @ 300 IN A 151.101.67.52
99
+ @ 300 IN ANAME t.sni.global.fastly.net.
100
+ * 300 IN CNAME t.sni.global.fastly.net.
101
+ _acme-challenge IN CNAME validation-abc123.acme-validations.example.net.
102
+ dev IN A 127.0.0.1
103
+ mail._domainkey IN CNAME mail.domainkey.abc123.mailprovider.example.net.
104
+ ssh 300 IN A 203.0.113.22
105
+
106
+ @ IN MX 10 mail.protonmail.ch.
107
+ @ IN MX 20 mailsec.protonmail.ch.
108
+
109
+ _imaps._tcp 1800 IN SRV 0 1 993 imap.fastmail.com.
110
+ _submission._tcp 1800 IN SRV 0 1 587 smtp.fastmail.com.
111
+
112
+ @ IN TXT "v=spf1 include:_spf.protonmail.ch ~all"
113
+ @ IN TXT "google-site-verification=abc123def456"
114
+ _dmarc IN TXT "v=DMARC1; p=quarantine"
115
+ ```
116
+
117
+ Things to notice:
118
+
119
+ - **`$TTL` is derived from your records** — it is set to the most common record TTL, and only records that deviate carry an explicit TTL (the `300` and `1800` above). The export is TTL-lossless: re-parsing it yields exactly the TTLs the provider reported.
120
+ - **ANAME records are preserved as ANAME.** ANAME is not a standard DNS record type (it exists only inside providers), and DME's own zone export *flattens* ANAMEs into resolved A-record snapshots — which go stale as soon as the target rotates. `dmez` keeps them first-class so the file remains a faithful, apply-able representation of your account. If you need an RFC-portable file (for BIND, or to migrate providers), pass `--strict-rfc` to the `dmez zone export` command: ANAMEs are flattened into their currently resolved A records, and a warning is printed for each conversion — if and only if a conversion happened.
121
+ - **Apex NS records are omitted by default** since DME manages them; pass `--include-apex-ns` to include them.
122
+ - Records are sorted and grouped deterministically, so exports diff cleanly in git.
123
+
124
+ Options: `--format=rfc|json|yaml` (default `rfc`), `--output=FILE`, `--ttl=N` (default TTL for records the provider returns without one), `--include-apex-ns`, `--strict-rfc`.
125
+
126
+ #### `dmez zone validate`
127
+
128
+ Parses a zone file and reports whether it is usable, with parse errors when it is not:
129
+
130
+ ```bash
131
+ $ dmez zone validate example.com.zone
132
+ ╔ OK ════════════════════════════════════════╗
133
+ ║ Zone file is valid. ║
134
+ ║ Records: 18 ║
135
+ ╚════════════════════════════════════════════╝
136
+ ```
137
+
138
+ Any valid zone file parses as-is — including raw exports from DNS providers with fully-qualified owners, tab separation, per-record TTLs, and SOA records (SOA is ignored, since DME manages it). You do **not** need to run `fmt` first.
139
+
140
+ #### `dmez zone fmt`
141
+
142
+ Rewrites any valid zone file into the canonical format shown above (stdout):
143
+
144
+ ```bash
145
+ dmez zone fmt provider-export.zone > example.com.zone
146
+ ```
147
+
148
+ #### `dmez zone plan`
149
+
150
+ The heart of the workflow: compares a zone file (desired state) against the live records (actual state) and prints what would change — without changing anything.
151
+
152
+ ```bash
153
+ $ dmez zone plan example.com.zone --domain=example.com
154
+ No changes.
155
+ ```
156
+
157
+ When there are differences, the plan groups them into sections:
158
+
159
+ ```text
160
+ Create
161
+ - www CNAME @ (ttl=300)
162
+ Update
163
+ - send TXT v=spf1 include:example.net ~all (ttl=60) -> send TXT v=spf1 include:example.org ~all (ttl=60)
164
+ Skipped Creates
165
+ - @ NS ns0.dnsmadeeasy.com. (ttl=86400) (Apex NS records are managed by the DNS provider)
166
+ Skipped Deletes
167
+ - old CNAME retired.example.net. (ttl=300) (Delete skipped by default)
168
+ Manual Review
169
+ - @ A 151.101.131.52 (ttl=300) -> @ A 140.248.151.52 (ttl=300) (Multiple records share the same owner/type identity (desired: 5, remote: 4))
170
+ ```
171
+
172
+ - **Create** — records in the file but not in the account.
173
+ - **Update** — records whose value changed (matched by owner and type).
174
+ - **Skipped Creates** — apex NS records from the file; DME manages these and the API will not create them.
175
+ - **Skipped Deletes** — records in the account but not in the file. Deletes never happen by default; see `apply --delete-only`.
176
+ - **Manual Review** — multiple records share the same owner/type identity and cannot be paired one-to-one (for example five apex `A` records in the file versus four in the account). The plan refuses to guess.
177
+
178
+ Options: `--domain=NAME` (defaults to the file's `$ORIGIN`), `--format=text|json`, `--diff-ttl`.
179
+
180
+ #### TTL Handling
181
+
182
+ `zone plan` and `zone apply` **ignore TTL-only differences by default**: a record whose only deviation from the remote is its TTL is considered unchanged, and value updates preserve the remote TTL. Pass `--diff-ttl` to treat TTLs as part of the record — TTL-only differences then become updates, and applied records take the TTL from the zone file:
183
+
184
+ ```bash
185
+ $ dmez zone plan example.com.zone --domain=example.com --diff-ttl
186
+ Update
187
+ - click CNAME links.cdn.example.net. (ttl=120) -> click CNAME links.cdn.example.net. (ttl=300)
188
+ ```
189
+
190
+ #### `dmez zone apply`
191
+
192
+ Executes the plan against DNS Made Easy. You will be shown the number of actions and asked to confirm (skip the prompt with `--yes`):
193
+
194
+ ```bash
195
+ $ dmez zone apply example.com.zone --domain=example.com
196
+ Apply 3 action(s)? Type yes to continue:
197
+ yes
198
+ ╔ ✔ OK ══════════════════════════════════════╗
199
+ ║ Zone apply complete for example.com. ║
200
+ ║ Applied: 3 ║
201
+ ║ Failed: 0 ║
202
+ ║ Skipped: 2 ║
203
+ ╚════════════════════════════════════════════╝
204
+ ```
205
+
206
+ Three modes control the blast radius:
207
+
208
+ | Mode | Flag | Behavior |
209
+ | --------------- | --------------------- | -------------------------------------------------------------------------- |
210
+ | Merge (default) | `--merge` | Applies creates and updates; never deletes. |
211
+ | Add only | `--add-only`, `-a` | Only creates missing records. |
212
+ | Delete only | `--delete-only`, `-d` | Only deletes records absent from the file (SOA and apex NS are protected). |
213
+
214
+ Actions run concurrently (4 workers) with progress spinners on stderr, so stdout stays clean for scripting.
215
+
216
+ #### A Complete Workflow
217
+
218
+ Setting up transactional email for a subdomain, start to finish:
219
+
220
+ ```bash
221
+ # 1. Read: export current state
222
+ dmez zone export mail.example.com --output=mail.example.com.zone
223
+
224
+ # 2. Edit: add the records your email provider asks for
225
+ $EDITOR mail.example.com.zone
226
+
227
+ # 3. Validate and review the plan
228
+ dmez zone validate mail.example.com.zone
229
+ dmez zone plan mail.example.com.zone --domain=mail.example.com
230
+
231
+ # 4. Apply (add-only is the safest mode for additive changes)
232
+ dmez zone apply mail.example.com.zone --domain=mail.example.com --add-only --yes
233
+
234
+ # 5. Verify: re-running plan should now be a no-op
235
+ dmez zone plan mail.example.com.zone --domain=mail.example.com
236
+ # => No changes.
237
+ ```
238
+
239
+ The edited file from step 2 might look like:
240
+
241
+ ```dns
242
+ $ORIGIN mail.example.com.
243
+ $TTL 300
244
+
245
+ click IN CNAME links.mailer.example.net.
246
+
247
+ @ IN MX 10 inbound-smtp.us-east-1.amazonaws.com.
248
+ send IN MX 10 feedback-smtp.us-east-1.amazonses.com.
249
+
250
+ _dmarc IN TXT "v=DMARC1; p=none;"
251
+ mailer._domainkey IN TXT "p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7abc123...AQAB"
252
+ send IN TXT "v=spf1 include:amazonses.com ~all"
253
+ ```
254
+
255
+ > [!NOTE]
256
+ > TXT and SPF values are stored unquoted internally and quoted exactly once in zone files — regardless of whether the DME API returns them with embedded quotes. You never need to worry about double-quoting.
257
+
258
+ ### Account Operations: `dmez account`
259
+
260
+ The `account` command exposes every operation of the underlying API client (the complete list of the legacy CLI):
261
+
262
+ ```bash
263
+ ❯ dmez account --list
264
+ all
265
+ base_url
266
+ create_a_record
267
+ create_aaaa_record
268
+ create_cname_record
269
+ create_domain
270
+ create_domains
271
+ create_httpred_record
272
+ create_mx_record
273
+ create_ns_record
274
+ create_ptr_record
275
+ create_record
276
+ create_secondary_domain
277
+ create_secondary_domains
278
+ create_secondary_ip_set
279
+ create_spf_record
280
+ create_srv_record
281
+ create_txt_record
282
+ delete_all_records
283
+ delete_domain
284
+ delete_record
285
+ delete_records
286
+ delete_secondary_domain
287
+ delete_secondary_ip_set
288
+ domain
289
+ domains
290
+ find_all
291
+ find_first
292
+ find_record_ids
293
+ get_id_by_domain
294
+ get_id_by_secondary_domain
295
+ records_for
296
+ secondary_domain
297
+ secondary_domains
298
+ secondary_ip_set
299
+ secondary_ip_sets
300
+ update_record
301
+ update_records
302
+ update_secondary_domains
303
+ update_secondary_ip_set
304
+ ```
305
+
306
+ Run `dmez account <operation> --help` for the signature of any operation, and pass `--format=json|json_pretty|yaml|pp` to control the output format:
307
+
308
+ ```bash
309
+ dmez account domains --format=json_pretty
310
+ dmez account records_for example.com
311
+ dmez account create_a_record example.com www 203.0.113.10
312
+ ```
313
+
314
+ <a name="ruby-api"></a>
315
+
316
+ ## Ruby API
317
+
318
+ **DnsMadeEasy** allows you to fetch, create, update DNS records from Ruby, as long as you know your API key and the secret.
319
+
320
+ ### Setting up Credentials
18
321
 
19
322
  Once you have the key and the secret, you have several choices:
20
323
 
21
- 1. You can directly instantiate a new instance of the `Client` class, by passing your API key and API secrets as arguments:
324
+ #### Credentials Methods for Both: CLI and Ruby API
22
325
 
23
- ```ruby
24
- require 'dnsmadeeasy'
25
- @client = DnsMadeEasy::Api::Client.new(api_key, api_secret)
26
- ```
326
+ > [!NOTE]
327
+ >
328
+ > Credentials used in these examples are random UUIDs generated specifically for these examples and are not actual valid API keys and a secret.
27
329
 
28
- 2. Or, you can use the `DnsMadeEasy.configure` method to configure the key/secret pair, and then use `DnsMadeEasy` namespace to call the methods:
330
+ - Set environment variables `DNSMADEEASY_API_KEY` and `DNSMADEEASY_API_SECRET`
29
331
 
30
- ```ruby
31
- require 'dnsmadeeasy'
332
+ - Put a `credentials.ini` file in the `~/.dnsmadeeasy` folder in your home, or in the current directory. The file's format is very simple:
32
333
 
33
- DnsMadeEasy.configure do |config|
34
- config.api_key = 'XXXX'
35
- config.api_secret = 'YYYY'
36
- end
334
+ ```ini
335
+ dns_dnsmadeeasy_api_key = 06cb6d10-63ca-013f-380b-66219a96f4e3
336
+ dns_dnsmadeeasy_secret_key = 14b9eb80-63ca-013f-380c-66219a96f4e3
337
+ ```
37
338
 
38
- DnsMadeEasy.domains.data.first.name #=> 'moo.gamespot.com'
39
- ```
40
-
41
- 3. Configuring API keys as above is easy, and can be done using environment variables. Alternatively, it may be convenient to store credentials in a YAML file.
42
-
43
- * If filename is not specified, there is default location where this file is searched, which is `~/.dnsmadeeasy/credentials.yml`.
44
- * If filename is provided, it will be read, and must conform to the following format:
45
-
46
- **Simple Credentials Format**
339
+ - If you prefer the YAML format, place the credentials into the file `~/.dnsmadeeasy/credentials.yml`:
47
340
 
48
341
  ```yaml
49
342
  # file: ~/.dnsmadeeasy/credentials.yml
50
343
  credentials:
51
- api_key: 2062259f-f666b17-b1fa3b48-042ad4030
52
- api_secret: 2265bc3-e31ead-95b286312e-c215b6a0
344
+ api_key: 06cb6d10-63ca-013f-380b-66219a96f4e3
345
+ api_secret: 14b9eb80-63ca-013f-380c-66219a96f4e3
53
346
  ```
54
347
 
55
- **Multi-Account Credentials Format**
56
-
57
- Below you see two accounts, with production key and secret being encrypted. See [further below](#encryption) about encrypting your key and secrets.
348
+ - Or set environment variable `DNSMADEEASY_CREDENTIALS_FILE` to whatever path your credentials live. Either YAML or INI format will be supported.
58
349
 
59
- ```yaml
60
- accounts:
61
- - name: development
62
- default_account: true
63
- credentials:
64
- api_key: 12345678-a8f8-4466-ffff-2324aaaa9098
65
- api_secret: 43009899-abcc-ffcc-eeee-09f809808098
66
- - name: production
67
- credentials:
68
- api_key: "BAhTOh1TeW06OkRhdGE6OldyYXBwZXJT............"
69
- api_secret: "BAhTOh1TeW06OkRhdGE6OldyYXBwZ............"
70
- encryption_key: spec/fixtures/sym.key
350
+ #### Credentials Methods Ruby API Only
351
+
352
+ - You can directly instantiate a new instance of the `Client` class, by passing your API key and API secrets as arguments:
353
+
354
+ ```ruby
355
+ require 'dnsmadeeasy'
356
+ @client = DnsMadeEasy::Api::Client.new(api_key, api_secret)
71
357
  ```
72
358
 
73
- You can use the following method to access both simple and multi-account YAML configurations:
74
-
359
+ - Or, you can use the `DnsMadeEasy.configure` method to configure the key/secret pair, and then use `DnsMadeEasy` namespace to call the methods:
360
+
75
361
  ```ruby
76
- require 'dnsmadeeasy'
77
- DnsMadeEasy.configure_from_file(file, account = nil, encryption_key = nil)
78
-
79
- # for example:
80
- DnsMadeEasy.configure_from_file('config/dme.yaml', 'production')
81
- DnsMadeEasy.domains #=> [ ... ]
82
-
83
- # or with encrypted key passed as an argument to decrypt YAML values:
84
- DnsMadeEasy.configure_from_file(
85
- 'config/dme.yaml',
86
- 'production',
87
- ENV['PRODUCTION_KEY'])
88
- ```
89
-
90
- 3. Finally, you can use `DME.credentials_from_file` method that, unlike the method above, uses hash arguments:
362
+ require 'dnsmadeeasy'
91
363
 
92
- ```ruby
93
- @creds = DnsMadeEasy.credentials_from_file(file: 'my-creds.yml',
94
- account: 'production',
95
- encryption_key: 'MY_KEY')
96
- @creds.api_key # => ...
97
- @creds.api_secret # => ...
98
- ```
364
+ DnsMadeEasy.configure do |config|
365
+ config.api_key = 'XXXX'
366
+ config.api_secret = 'YYYY'
367
+ end
368
+
369
+ DnsMadeEasy.domains.data.first.name #=> 'moo.gamespot.com'
370
+ ```
371
+
372
+ ### Multi-Account Credentials File Format
373
+
374
+ Below you see two accounts, with production key and secret being encrypted. See [further below](#encryption) about encrypting your key and secrets.
99
375
 
100
- Method above simply returns the credentials instance, but does not "save" it as the default credentials like `configure_from_file`. Therefore, if you need to access multiple accounts at the same time, this method will help you maintain multiple credentials at the same time.
376
+ ```yaml
377
+ accounts:
378
+ - name: development
379
+ default_account: true
380
+ credentials:
381
+ api_key: 12345678-a8f8-4466-ffff-2324aaaa9098
382
+ api_secret: 43009899-abcc-ffcc-eeee-09f809808098
383
+ - name: production
384
+ credentials:
385
+ api_key: "BAhTOh1TeW06OkRhdGE6OldyYXBwZXJT............"
386
+ api_secret: "BAhTOh1TeW06OkRhdGE6OldyYXBwZ............"
387
+ encryption_key: spec/fixtures/sym.key
388
+ ```
389
+
390
+ You can use the following method to access both simple and multi-account YAML configurations:
391
+
392
+ ```ruby
393
+ require 'dnsmadeeasy'
394
+ DnsMadeEasy.configure_from_file(file, account = nil, encryption_key = nil)
395
+
396
+ # for example:
397
+ DnsMadeEasy.configure_from_file('config/dme.yaml', 'production')
398
+ DnsMadeEasy.domains #=> [ ... ]
399
+
400
+ # or with encrypted key passed as an argument to decrypt YAML values:
401
+ DnsMadeEasy.configure_from_file(
402
+ 'config/dme.yaml',
403
+ 'production',
404
+ ENV['PRODUCTION_KEY'])
405
+ ```
406
+
407
+ Finally, you can use `DME.credentials_from_file` method that, unlike the method above, uses hash arguments:
408
+
409
+ ```ruby
410
+ @creds = DnsMadeEasy.credentials_from_file(file: 'my-creds.yml',
411
+ account: 'production',
412
+ encryption_key: 'MY_KEY')
413
+ @creds.api_key # => ...
414
+ @creds.api_secret # => ...
415
+ ```
101
416
 
102
- ___
417
+ Method above simply returns the credentials instance, but does not "save" it as the default credentials like `configure_from_file`. Therefore, if you need to access multiple accounts at the same time, this method will help you maintain multiple credentials at the same time.
103
418
 
104
419
  Once you configure the keys, you can also use the shortcut module to save you some typing:
105
420
 
@@ -108,36 +423,31 @@ require 'dnsmadeeasy/dme'
108
423
  DME.domains.data.first.name #=> 'moo.gamespot.com'
109
424
  ```
110
425
 
111
- This has the advantage of being much shorter, but might conflict with existing modules in your Ruby VM.
426
+ This has the advantage of being much shorter, but might conflict with existing modules in your Ruby VM.
112
427
  In this case, just do not require `dnsmadeeasy/dme` and only require `dnsmadeeasy`, and you'll be fine.
113
428
  Otherwise, using `DME` is identical to using `DnsMadeEasy`, assuming you required `dnsmadeeasy/dme` file.
114
-
115
429
 
116
430
  ### Which Namespace to Use? What is `DME` versus `DnsMadeEasy`?
117
431
 
118
432
  Since `DnsMadeEasy` is a bit of a mouthful, we decided to offer (in addition to the standard `DnsMadeEasy` namespace) the abbreviated module `DME` that simply forwards all messages to the module `DnsMadeEasy`. If in your Ruby VM there is no conflicting top-level class `DME`, then you can `require 'dnsmadeeasy/dme'` to get all of the DnsMadeEasy client library functionality without having to type the full name once. You can even do `require 'dme'`.
119
433
 
120
- Whenever you require `dme` you also import the `DnsMadeEasy` namespace. **The opposite is not true.**
434
+ Whenever you require `dme` you also import the `DnsMadeEasy` namespace. **The opposite is not true.**
121
435
 
122
436
  So if you DO have a name clash with another top-level module `DME`, simply do `require 'dnsmadeeasy'` and none of the `DME` module namespace will be loaded.
123
437
 
124
438
  In a nutshell you have three ways to access all methods provided by the [`DnsMadeEasy::Api::Client`](http://www.rubydoc.info/gems/dnsmadeeasy/DnsMadeEasy/Api/Client) class:
125
439
 
126
- 1. Instantiate and use the client class directly,
127
- 2. Use the top-level module `DnsMadeEasy` with `require 'dnsmadeeasy'`
128
- 3. Use the shortened top-level module `DME` with `require 'dnsmadeeasy/dme'`
129
-
130
-
440
+ 1. Instantiate and use the client class directly,
441
+ 1. Use the top-level module `DnsMadeEasy` with `require 'dnsmadeeasy'`
442
+ 1. Use the shortened top-level module `DME` with `require 'dnsmadeeasy/dme'`
443
+
131
444
  ### Examples
132
445
 
133
446
  Whether or not you are accessing a single account or multiple, it is recommended that you save your credentials (the API key and the secret) encrypted in the above mentioned file `~/.dnsmadeeasy/credentials.yml` (or any file of you preference).
134
- ___
135
447
 
136
- > **NOTE:**
137
- >
138
- > * DO NOT check that file into your repo!
139
- > * If you use encryption, do not check in your key!
140
- ___
448
+ > [!WARNING]
449
+ >
450
+ > _**DO NOT check that file into your repo! If you use encryption, do not check in your key!**_
141
451
 
142
452
  The examples that follow assume credentials have already been configured, and so we explore the API.
143
453
 
@@ -161,7 +471,7 @@ IRB > DME.api_key
161
471
 
162
472
  IRB > DME.api_secret
163
473
  ⤷ "2265bc3-e31ead-95b286312e-c215b6a0"
164
-
474
+
165
475
  IRB > DME.domain('gamespot.live').delegateNameServers
166
476
  ⤷ #<Hashie::Array ["ns-125-c.gandi.net.", "ns-129-a.gandi.net.", "ns-94-b.gandi.net."]>
167
477
 
@@ -173,7 +483,7 @@ IRB > @client = DME.client
173
483
  @options={},
174
484
  @requests_remaining=149,
175
485
  @request_limit=150,
176
- @base_uri="https://api.dnsmadeeasy.com/V2.0">
486
+ @base_uri="https://api.dnsmadeeasy.com/V2.0">
177
487
  ```
178
488
 
179
489
  Next, let's fetch a particular domain, get it's records and compute the counts for each record type, such as 'A', 'NS', etc.
@@ -186,7 +496,7 @@ IRB > records.data.select{|f| f.type == 'A' }.map(&:name)
186
496
  ⤷ ["www", "vpn-us-east1", "vpn-us-east2", "staging", "yourmom"]
187
497
  IRB > types = records.data.map(&:type)
188
498
  ⤷ [....]
189
- IRB > require 'awesome_print'
499
+ IRB > require 'awesome_print'
190
500
  IRB > ap Hash[types.group_by {|x| x}.map {|k,v| [k,v.count]}]
191
501
  {
192
502
  "MX" => 2,
@@ -201,217 +511,82 @@ IRB > ap Hash[types.group_by {|x| x}.map {|k,v| [k,v.count]}]
201
511
 
202
512
  All public methods of this library return a Hash-like object, that is actually an instance of the class [`Hashie::Mash`](https://github.com/intridea/hashie). `Hashie::Mash` supports the very useful ability to reach deeply nested hash values via a chain of method calls instead of using a train of square brackets. You can always convert it to a regular hash either `to_hash` or `to_h` on an instance of a `Hashie::Mash` to get a pure hash representation.
203
513
 
204
- > NOTE: `to_hash` converts the entire object to a regular hash, including the deeply nested hashes, while `to_h` only converts the primary object, but not the nested hashes. Here is an example below — in the first instance where we call `to_h` we are still able to call `.value` on the nested object, because only the top-level `Mash` has been converted into a `Hash`. In the second example, this call fails, because this method does not exist, and the value must be accessed via the square brackets:
205
- >
206
- > ```ruby
207
- > IRB > recs.to_h['data'].last.value
208
- > ⤷ "54.200.26.233"
209
- > IRB > recs.to_hash['data'].last.value
210
- > "NoMethodError: undefined method `value` for #<Hash:0x00007fe36fab0f68>"
211
- > IRB > recs.to_hash['data'].last['value']
212
- > ⤷ "54.200.26.233"
213
- > ```
214
-
215
- For more information on the actual JSON API, please refer to the [following PDF document](http://www.dnsmadeeasy.com/integration/pdf/API-Docv2.pdf).
216
-
217
- ## Available Actions
218
-
219
- Here is the complete of all methods supported by the `DnsMadeEasy::Api::Client`:
220
-
221
- #### Domains
222
-
223
- * `create_domain`
224
- * `create_domains`
225
- * `delete_domain`
226
- * `domain`
227
- * `domains`
228
- * `get_id_by_domain`
229
-
230
- #### Records
231
-
232
- * `records_for`
233
- * `all`
234
- * `base_uri`
235
- * `create_a_record`
236
- * `create_aaaa_record`
237
- * `create_cname_record`
238
- * `create_httpred_record`
239
- * `create_mx_record`
240
- * `create_ns_record`
241
- * `create_ptr_record`
242
- * `create_record`
243
- * `create_spf_record`
244
- * `create_srv_record`
245
- * `create_txt_record`
246
- * `delete_all_records`
247
- * `delete_record`
248
- * `delete_records`
249
- * `find_all`
250
- * `find_first`
251
- * `find_record_ids`
252
-
253
- #### Secondary Domains
254
- * `secondary_domain`
255
- * `secondary_domains`
256
- * `get_id_by_secondary_domain`
257
- * `create_secondary_domain`
258
- * `create_secondary_domains`
259
- * `update_secondary_domains`
260
- * `delete_secondary_domain`
261
-
262
- #### Secondary IpSets
263
- * `secondary_ip_set`
264
- * `secondary_ip_sets`
265
- * `create_secondary_ip_set`
266
- * `update_secondary_ip_set`
267
- * `delete_secondary_ip_set`
268
-
269
- <a name="encryption"></a>
270
-
271
- ### Encryption
514
+ > [!NOTE]
515
+ >
516
+ > `to_hash` converts the entire object to a regular hash, including the deeply nested hashes, while `to_h` only converts the primary object, but not the nested hashes. Here is an example below -- in the first instance where we call `to_h` we are still able to call `.value` on the nested object, because only the top-level `Mash` has been converted into a `Hash`. In the second example, this call fails, because this method does not exist, and the value must be accessed via the square brackets:
272
517
 
273
- It was mentioned above that the credentials YAML file may contain encrypted values. This facility is provided by the encryption gem [Sym](https://github.com/kigster/sym).
274
518
 
275
- In order to encrypt your values, you need to perform the following steps:
276
519
 
277
- ```bash
278
- gem install sym
279
-
280
- # let's generate a new key and save it to a file:
281
- sym -g -o my.key
282
-
283
- # if you are on Mac OS-X, you can import the key into the KeyChain.
284
- # this creates an entry in the keychain named 'my.key' that can be used later.
285
- sym -g -x my.key
286
- ```
287
-
288
- Once you have the key generated, first, **make sure to never commit this to any repo!**. You can use 1Password for it, or something like that.
289
-
290
- Let's encrypt our actual API key:
291
-
292
- ```bash
293
- api_key="12345678-a8f8-4466-ffff-2324aaaa9098"
294
- api_secret="43009899-abcc-ffcc-eeee-09f809808098"
295
- sym -ck my.key -e -s "${api_key}"
296
- # => prints the encrypted value
297
-
298
- # On a mac, you can copy it to clipboard:
299
- sym -ck my.key -e -s "${api_secret}" | pbcopy
520
+ ```ruby
521
+ IRB > recs.to_h['data'].last.value
522
+ ⤷ "54.200.26.233"
523
+ IRB > recs.to_hash['data'].last.value
524
+ "NoMethodError: undefined method `value` for #<Hash:0x00007fe36fab0f68>"
525
+ IRB > recs.to_hash['data'].last['value']
526
+ "54.200.26.233"
300
527
  ```
301
528
 
302
- Now, you place the encrypted values in the YAML file, and you can save "my.key" as the value against `encryption_key:` at the same level as the `api_key` and `api_secret` in the YAML file. This value can either point to a file path, or be a keychain name, or even a name of an environment variable. For full details, please see [sym documentation](https://github.com/kigster/sym#using-sym-with-the-command-line).
529
+ For more information on the actual JSON API, please refer to the [following PDF document](http://www.dnsmadeeasy.com/integration/pdf/API-Docv2.pdf).
303
530
 
304
- ## CLI Client
531
+ ### Available Actions
305
532
 
306
- This library offers a simple CLI client `dme` that maps the command line arguments to method arguments for corresponding actions:
533
+ Here is the complete list of all methods supported by the `DnsMadeEasy::Api::Client`:
307
534
 
308
- ```bash
309
- ❯ dme --help
310
- Usage:
311
- # Execute an API call:
312
- dme [ --json | --yaml ] operation [ arg1 arg2 ... ]
535
+ #### Domains
313
536
 
314
- # Print suported operations:
315
- dme op[erations]
537
+ - `create_domain`
538
+ - `create_domains`
539
+ - `delete_domain`
540
+ - `domain`
541
+ - `domains`
542
+ - `get_id_by_domain`
316
543
 
317
- Credentials:
318
- Store your credentials in a YAML file
319
- /Users/kig/.dnsmadeeasy/credentials.yml as follows:
544
+ #### Records
320
545
 
321
- credentials:
322
- api_key: XXXX
323
- api_secret: YYYY
546
+ - `records_for`
547
+ - `all`
548
+ - `base_uri`
549
+ - `create_a_record`
550
+ - `create_aaaa_record`
551
+ - `create_cname_record`
552
+ - `create_httpred_record`
553
+ - `create_mx_record`
554
+ - `create_ns_record`
555
+ - `create_ptr_record`
556
+ - `create_record`
557
+ - `create_spf_record`
558
+ - `create_srv_record`
559
+ - `create_txt_record`
560
+ - `delete_all_records`
561
+ - `delete_record`
562
+ - `delete_records`
563
+ - `find_all`
564
+ - `find_first`
565
+ - `find_record_ids`
324
566
 
325
- Examples:
326
- dme domain moo.com
327
- dme --json domain moo.com
328
- dme find_all moo.com A www
329
- dme find_first moo.com CNAME vpn-west
330
- dme --yaml find_first moo.com CNAME vpn-west
331
- ```
332
-
333
- You can run `dme operations` to see the supported list of operations:
567
+ #### Secondary Domains
334
568
 
335
- ```bash
336
- dme op
337
- Actions:
338
- Checkout the README and RubyDoc for the arguments to each operation,
339
- which is basically a method on a DnsMadeEasy::Api::Client instance.
340
- http://www.rubydoc.info/gems/dnsmadeeasy/DnsMadeEasy/Api/Client
341
-
342
- Valid Operations Are:
343
- all
344
- base_uri
345
- create_a_record
346
- create_aaaa_record
347
- create_cname_record
348
- create_domain
349
- create_domains
350
- create_httpred_record
351
- create_mx_record
352
- create_ns_record
353
- create_ptr_record
354
- create_record
355
- create_secondary_domain
356
- create_secondary_domains
357
- create_secondary_ip_set
358
- create_spf_record
359
- create_srv_record
360
- create_txt_record
361
- delete_all_records
362
- delete_domain
363
- delete_record
364
- delete_records
365
- delete_secondary_domain
366
- delete_secondary_ip_set
367
- domain
368
- domains
369
- find_all
370
- find_first
371
- find_record_ids
372
- get_id_by_domain
373
- get_id_by_secondary_domain
374
- records_for
375
- secondary_domain
376
- secondary_domains
377
- secondary_ip_set
378
- secondary_ip_sets
379
- update_record
380
- update_records
381
- update_secondary_domains
382
- update_secondary_ip_set
383
- ```
384
-
385
- For example:
569
+ - `secondary_domain`
570
+ - `secondary_domains`
571
+ - `get_id_by_secondary_domain`
572
+ - `create_secondary_domain`
573
+ - `create_secondary_domains`
574
+ - `update_secondary_domains`
575
+ - `delete_secondary_domain`
386
576
 
387
- ```bash
388
- ❯ dme domains moo.com
389
- ```
577
+ #### Secondary IpSets
390
578
 
391
- is equivalent to `DME.domains("moo.com")`. You can use any operation listed above, and output the result in either `YAML` or `JSON` (in addition to the default "awesome_print"), for example:
392
-
393
- ```bash
394
- dme --yaml find_all moo.com www CNAME
395
- ---
396
- - dynamicDns: false
397
- failed: false
398
- gtdLocation: DEFAULT
399
- hardLink: false
400
- ttl: 60
401
- failover: false
402
- monitor: false
403
- sourceId: 5861234
404
- source: 1
405
- name: www
406
- value: ec2-54-202-251-7.us-west-2.compute.amazonaws.com
407
- id: 43509989
408
- type: CNAME
409
- ```
579
+ - `secondary_ip_set`
580
+ - `secondary_ip_sets`
581
+ - `create_secondary_ip_set`
582
+ - `update_secondary_ip_set`
583
+ - `delete_secondary_ip_set`
410
584
 
411
585
  ### Managing Domains
412
586
 
413
- > NOTE: below we can be using `@client` instantiated with given key and secret, or
414
- > `DME` or `DnsMadeEasy` module.
587
+ > [!NOTE]
588
+ >
589
+ > Below we can be using `@client` instantiated with given key and secret, or `DME` or `DnsMadeEasy` module.
415
590
 
416
591
  To retrieve all domains:
417
592
 
@@ -449,6 +624,7 @@ DME.delete_domain ('test.io')
449
624
  ### Managing Secondary Domains
450
625
 
451
626
  To retrieve all secondary domains:
627
+
452
628
  ```ruby
453
629
  DME.secondary_domains
454
630
  ```
@@ -478,8 +654,8 @@ DME.create_secondary_domains(%w[test.io moo.re], IP_SET_ID)
478
654
  To update a secondary domain:
479
655
 
480
656
  ```ruby
481
- # IP_SET_ID is id of ip_set you want to associate
482
- # DOMAIN_ID is id of domain
657
+ # IP_SET_ID is id of ip_set you want to associate
658
+ # DOMAIN_ID is id of domain
483
659
  DME.update_secondary_domains([DOMAIN_ID], IP_SET_ID)
484
660
  ```
485
661
 
@@ -492,6 +668,7 @@ DME.delete_secondary_domain('test.io')
492
668
  ### Managing Secondary IpSets
493
669
 
494
670
  To retrieve all secondary IpSets:
671
+
495
672
  ```ruby
496
673
  DME.secondary_ip_sets
497
674
  ```
@@ -572,7 +749,7 @@ Below are the method calls for `MX`, `SRV`, and `HTTPRED` types:
572
749
  DME.create_mx_record ('test.io', 'woah', 5, '127.0.0.1', {})
573
750
  # Arguments are: domain_name, name, priority, weight, port, value, options = {}
574
751
  DME.create_srv_record ('test.io', 'woah', 1, 5, 80, '127.0.0.1', {})
575
- # Arguments are: domain_name, name, value, redirectType,
752
+ # Arguments are: domain_name, name, value, redirectType,
576
753
  DME.create_httpred_record('test.io', 'woah', '127.0.0.1', 'STANDARD - 302',
577
754
  # description, keywords, title, options = {}
578
755
  'a description', 'keywords', 'a title', {})
@@ -595,7 +772,6 @@ DME.update_records('test.io',
595
772
  'value'=> '127.0.0.1'
596
773
  }
597
774
  ], { 'ttl' => '60' })
598
-
599
775
  ```
600
776
 
601
777
  To get the number of API requests remaining after a call:
@@ -604,59 +780,79 @@ To get the number of API requests remaining after a call:
604
780
  DME.requests_remaining
605
781
  #=> 19898
606
782
  ```
607
- > NOTE: Information is not available until an API call has been made
783
+
784
+
785
+
786
+ > [!NOTE]
787
+ > Information is not available until an API call has been made.
608
788
 
609
789
  To get the API request total limit after a call:
610
790
 
611
791
  ```ruby
612
792
  DME.request_limit
613
793
  #=> 2342
794
+
614
795
  ```
615
- > NOTE: Information is not available until an API call has been made
616
796
 
797
+ <a name="encryption"></a>
617
798
 
618
- ## Installation
799
+ ### Encryption
619
800
 
620
- Add this line to your application's Gemfile:
801
+ It was mentioned above that the credentials YAML file may contain encrypted values. This facility is provided by the encryption gem [Sym](https://github.com/kigster/sym).
621
802
 
622
- ```ruby
623
- gem 'dnsmadeeasy'
624
- ```
803
+ > [!IMPORTANT]
804
+ >
805
+ > There is a much better encryption facility called `sopsy` written in Rust. We highly recommend you use `sopsy` for encrypting your secrets, as it mints private keys on a Mac inside Appple Enclave hardware chip. For more information please see [sopsy's website](https://sopsy-cli.dev) or the [Github Project](https://github.com/kigster/sopsy).
625
806
 
626
- And then execute:
807
+ ------
627
808
 
628
- ```
629
- $ bundle
630
- ```
809
+ In order to encrypt your values, you need to perform the following steps:
631
810
 
632
- Or install it yourself:
811
+ ```bash
812
+ gem install sym
633
813
 
814
+ # let's generate a new key and save it to a file:
815
+ sym -g -o my.key
816
+
817
+ # if you are on Mac OS-X, you can import the key into the KeyChain.
818
+ # this creates an entry in the keychain named 'my.key' that can be used later.
819
+ sym -g -x my.key
634
820
  ```
635
- $ gem install dnsmadeeasy
821
+
822
+ Once you have the key generated, first, **make sure to never commit this to any repo!**. You can use 1Password for it, or something like that.
823
+
824
+ Let's encrypt our actual API key:
825
+
826
+ ```bash
827
+ api_key="12345678-a8f8-4466-ffff-2324aaaa9098"
828
+ api_secret="43009899-abcc-ffcc-eeee-09f809808098"
829
+ sym -ck my.key -e -s "${api_key}"
830
+ # => prints the encrypted value
831
+
832
+ # On a mac, you can copy it to clipboard:
833
+ sym -ck my.key -e -s "${api_secret}" | pbcopy
636
834
  ```
637
835
 
836
+ Now, you place the encrypted values in the YAML file, and you can save "my.key" as the value against `encryption_key:` at the same level as the `api_key` and `api_secret` in the YAML file. This value can either point to a file path, or be a keychain name, or even a name of an environment variable. For full details, please see [sym documentation](https://github.com/kigster/sym#using-sym-with-the-command-line).
638
837
 
639
838
  ## Development
640
839
 
641
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exe rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
840
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bundle exec rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
642
841
 
643
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, up date the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
842
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
644
843
 
645
844
  ## Acknowledgements
646
845
 
647
846
  The current maintainer [Konstantin Gredeskoul](https://github.com/kigster) wishes to thank:
648
847
 
649
- * Arnoud Vermeer for the original `dnsmadeeasy-rest-api` gem
650
- * Andre Arko, Paul Henry, James Hart formerly of [Wanelo](wanelo.com) fame, for bringing the REST API gem up to the level.
651
- * Phil Cohen, who graciously transferred the ownership of the name of this gem on RubyGems.org to the current maintainer.
652
-
848
+ - *Arnoud Vermeer* for the original `dnsmadeeasy-rest-api` gem
849
+ - *Andre Arko, Paul Henry, James Hart* formerly of [Wanelo](wanelo.com) fame, for bringing the REST API gem up to the level.
850
+ - *Phil Cohen*, who graciously transferred the ownership of the name of this gem on RubyGems.org to the current maintainer.
653
851
 
654
852
  ## Contributing
655
853
 
656
- Bug reports and pull requests are welcome on GitHub at [https://github.com/kigster/dnsmadeeasy](https://github.com/kigster/dnsmadeeasy).
854
+ Bug reports and pull requests are welcome on GitHub at <https://github.com/kigster/dnsmadeeasy>.
657
855
 
658
856
  ## License
659
857
 
660
858
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
661
-
662
-