Skip to content

Commit a56e3d6

Browse files
committed
Merge pull request #5 from wk8/possibly_several_records_with_same_name
v0.1.6: Added the `shared_A_record` attribute
2 parents eeee46c + 0a1368d commit a56e3d6

File tree

12 files changed

+311
-80
lines changed

12 files changed

+311
-80
lines changed

Berksfile.lock

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@ DEPENDENCIES
44
metadata: true
55

66
GRAPH
7-
cloudflare (0.1.4)
7+
cloudflare (0.1.6)

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
This file is used to list changes made in each version of cloudflare.
44

5+
## 0.1.6:
6+
7+
* Added the `shared_A_record` attribute to the LWRP to make it possible to have several A records with the same name (aka DNS load balancing)
8+
* Properly updating LWRP states when an action has been performed
9+
* More complete example recipes (should integrate Test Kitchen soon)
10+
511
## 0.1.5:
612

713
* Included Vagrant & Berkshelf for easier development

README.md

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ I strongly recommend storing your Cloudflare credentials in an encrypted data ba
1919

2020
There also are a number of optional node attributes:
2121

22-
* `['cloudflare']['check_credentials']`: whether to check your Cloudflare credentials before attempting any API call (defaults to `true`)
23-
* `['cloudflare']['check_zone']`: whether to check if the specified DNS zone(s) exist(s) on your Cloudflare account before trying to add or delete records (defaults to `true`)
24-
* `['cloudflare']['check_with_DNS']`: whether to try checking your records' existence against a DNS server rather than querying the Cloudflare API (will still query the Cloudflare API if the DNS server does not return what's expected - defaults to `false`)
22+
* `['cloudflare']['debug']`: used to defined other default attributes (see below - defaults to `false`); basically, you might want to turn that to true when setting up your cookbook, but **be sure to turn that back to `false` when running this cookbook on your actual servers to avoid hitting Cloudflare's API thresholds**
23+
* `['cloudflare']['check_credentials']`: whether to check your Cloudflare credentials before attempting any API call (defaults to `node['cloudflare']['debug']`)
24+
* `['cloudflare']['check_zone']`: whether to check if the specified DNS zone(s) exist(s) on your Cloudflare account before trying to add or delete records (defaults to `node['cloudflare']['debug']`)
25+
* `['cloudflare']['check_with_DNS']`: whether to try checking your records' existence against a DNS server rather than querying the Cloudflare API (will still query the Cloudflare API if the DNS server does not return what's expected - defaults to `true`)
2526
* `['cloudflare']['DNS_server']`: if you use the `check_with_DNS` option, that is the DNS server that will be queried (defaults to `ns.cloudflare.com`, which is Cloudflare's main public DNS server)
2627

2728
Those attributes come in especially handy if you have a number of servers and get throttled by Cloudflare's API limits.
@@ -41,7 +42,8 @@ This resource defines the following attributes (they're all `String`s unless oth
4142
* `zone` (required): the zone of the DNS record
4243
* `content` (optional - defaults to `node.ipaddress`): the content of the DNS record
4344
* `type` (one of `'A'` or `'CNAME'` - defaults to `'A'`): the type of the DNS record. Please let me know if you'd like other record types supported
44-
* `ttl` (must be a `Fixnum` - defaults to `1`, which, according to Cloudflare doc, means 'automatic'): the ttl of the DNS record
45+
* `ttl` (must be a `Fixnum` - defaults to `1`, which, according to Cloudflare's doc, means 'automatic'): the ttl of the DNS record
46+
* `shared_A_record` (boolean - optional, defaults to `false`): set that to `true` if you want to have several A records with the same name (aka DNS load balancing). Also works for deleting a single A record when several share the same name. Disclaimer: IMHO, that's [pretty bad practice](http://bitplex.me/2008/09/why-round-robin-dns-is-bad.html). This attribute is totally ignored for CNAME records.
4547

4648
For instance, the following code in your cookbook's recipe would create an `A` DNS record `server_name.example.com` pointing to `1.2.3.4` with an automatic TTL:
4749

@@ -82,7 +84,9 @@ You also need to define 3 environment variables to be able to use my Vagrantfile
8284

8385
You can do so by typing e.g. `export CLOUDFLARE_EMAIL='[email protected]'` and so on in your shell.
8486

85-
Be aware that the example recipe will then proceed to create a few DNS records on that DNS zone with your credentials, so use with caution!
87+
Be aware that the example recipe will then proceed to create a few DNS records on that DNS zone with your credentials, so use with caution! That being said, all said records will start with 'cl-cb-test-' so they have little chance of clonflicting with exisiting records on your account.
88+
89+
You can also easily clean up the test records created that way by running `CLOUDFLARE_CLEANUP=1 vagrant provision`.
8690

8791
Then playing with this cookbook should be as easy as running `bundle install && vagrant up`!
8892

@@ -95,6 +99,11 @@ Feel free to reach me at <[email protected]>
9599
Changes
96100
=======
97101

102+
* 0.1.6 (Jul 9, 2014)
103+
* Added the `shared_A_record` attribute to the LWRP to make it possible to have several A records with the same name (aka DNS load balancing)
104+
* Properly updating LWRP states when an action has been performed
105+
* More complete example recipes (should integrate Test Kitchen soon)
106+
98107
* 0.1.5 (Jul 8, 2014)
99108
* Included Vagrant & Berkshelf for easier development
100109

Vagrantfile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
CLOUDFLARE_EMAIL = ENV['CLOUDFLARE_EMAIL']
55
CLOUDFLARE_API_KEY = ENV['CLOUDFLARE_API_KEY']
6-
CLOUDFLARE_ZONE = ENV['CLOUDFLARE_DOMAIN']
6+
CLOUDFLARE_ZONE = ENV['CLOUDFLARE_ZONE']
77

88
if !CLOUDFLARE_EMAIL || !CLOUDFLARE_API_KEY || !CLOUDFLARE_ZONE
99
raise 'You must define the CLOUDFLARE_EMAIL, CLOUDFLARE_API_KEY and CLOUDFLARE_ZONE environment variables'
@@ -28,12 +28,16 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
2828
'email' => CLOUDFLARE_EMAIL,
2929
'api_key' => CLOUDFLARE_API_KEY
3030
},
31-
'example_zone' => CLOUDFLARE_ZONE
31+
'example_zone' => CLOUDFLARE_ZONE,
32+
'debug' => true
3233
}
3334
}
3435

3536
chef.run_list = [
3637
'recipe[cloudflare::example]',
3738
]
39+
if ENV['CLOUDFLARE_CLEANUP']
40+
chef.run_list << 'recipe[cloudflare::example-cleanup]'
41+
end
3842
end
3943
end

attributes/default.rb

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
# If set to true, will perform all possible checks on your credentials and so on
2+
# You most likely want that switched to `false' when you're done setting up your
3+
# cookbook! That way we'll make as few calls to Cloudflare API as possible
4+
default['cloudflare']['debug'] = false
5+
16
# If set to true, we'll check your credentials are valid before carrying on
2-
default['cloudflare']['check_credentials'] = true
7+
default['cloudflare']['check_credentials'] = node['cloudflare']['debug']
38
# If set to true, we'll check the specified zone exists before trying to create
49
# or delete new records in it
5-
default['cloudflare']['check_zone'] = true
10+
default['cloudflare']['check_zone'] = node['cloudflare']['debug']
611

712
# Set that attribute below to true if you decide to check your records by
813
# querying a DNS server instead of Cloudflare directly (can be useful to avoid
914
# making too many requests to Cloudflare if you have a number of servers
10-
default['cloudflare']['check_with_DNS'] = false
15+
default['cloudflare']['check_with_DNS'] = true
1116
# If you set the attribute above to true, that's the DNS server we're going
1217
# to ask - defaults to Cloudflare's main public DNS server
1318
default['cloudflare']['DNS_server'] = 'ns.cloudflare.com'

libraries/cloudflare.rb

Lines changed: 66 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,50 @@ class Connection
77

88
# adding a few useful methods to the vanilla Cloudflare library
99
class CloudflareClient < CloudFlare::Connection
10-
1110
# custom timeout to avoid timing out
1211
TIMEOUT = 10
1312

14-
# This method deletes a DNS record by name
13+
# This method deletes DNS records by name
1514
#
1615
# @param zone [String]
1716
# @param name [String]
1817
def rec_delete_by_name zone, name
19-
rec_id = get_record(zone, name)['rec_id'] or return false
20-
rec_delete(zone, rec_id)
18+
get_records(zone, name).keys.each { |rec_id| rec_delete(zone, rec_id) }
19+
end
20+
21+
# This method finds a record by its name, zone, content and type,
22+
# Returns nil of none was found, the meta hash otherwise
23+
#
24+
# @param zone [String]
25+
# @param name [String]
26+
# @param content [String]
27+
# @param type [String]
28+
def find_single_record zone, name, content, type = 'A'
29+
get_records(zone, name).values.each do |rec|
30+
if rec['zone_name'] == zone \
31+
&& rec['display_name'] == name \
32+
&& rec['content'] == content \
33+
&& rec['type'] == type
34+
return rec
35+
end
36+
end
37+
nil
38+
end
39+
40+
# This method deletes a single record
41+
# Returns true iff a record was deleted
42+
#
43+
# @param zone [String]
44+
# @param name [String]
45+
# @param content [String]
46+
# @param type [String]
47+
def rec_delete_single zone, name, content, type = 'A'
48+
rec = find_single_record(zone, name, content, type) or return false
49+
rec_delete(zone, rec['rec_id'])
50+
true
2151
end
2252

23-
# This method returns true if that zone exists
53+
# This method returns true iff that zone exists
2454
#
2555
# @param zone [String]
2656
def zone_exists? zone
@@ -35,15 +65,20 @@ def credentials_valid?
3565
zone_load_multi['result'] == 'success' rescue false
3666
end
3767

38-
# Gets all the metadata hash for a given record
68+
# Gets all the metadata for records that match
69+
# the given `zone' and `name'
70+
# Returns a hash mapping record's IDs to the metadata hashes
3971
#
4072
# @param zone [String]
4173
# @param name [String]
42-
def get_record zone, name
74+
def get_records zone, name
75+
result = {}
4376
get_all_records_for_zone(zone).each do |rec|
44-
return rec if rec['display_name'] == name && rec['zone_name'] == zone
45-
end rescue NoMethodError
46-
nil
77+
if rec['display_name'] == name && rec['zone_name'] == zone
78+
result[rec['rec_id']] = rec
79+
end
80+
end
81+
result
4782
end
4883

4984
# The vanilla lib's 'rec_load_all' implementation doesn't account (as of v2.0.1)
@@ -56,6 +91,20 @@ def rec_load_all zone, offset = 0
5691
send_req({a: :rec_load_all, z: zone, o: offset})
5792
end
5893

94+
# we need to flush the cache when creating, editing or deleting records
95+
def rec_new zone, *args
96+
flush_cache_for_zone zone
97+
super zone, *args
98+
end
99+
def rec_edit zone, *args
100+
flush_cache_for_zone zone
101+
super zone, *args
102+
end
103+
def rec_delete zone, *args
104+
flush_cache_for_zone zone
105+
super zone, *args
106+
end
107+
59108
private
60109

61110
# Returns an array containing all the records for this zone,
@@ -85,4 +134,11 @@ def zone_load_multi
85134
@zone_load_multi_cache ||= super
86135
end
87136

137+
# We need to flush the cache when we make changes that make it obsolete
138+
#
139+
# @param zone [String]
140+
def flush_cache_for_zone zone
141+
@records_cache ||= {}
142+
@records_cache.delete zone
143+
end
88144
end

0 commit comments

Comments
 (0)