google-cloud-dns 0.20.0

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.
@@ -0,0 +1,176 @@
1
+ # Copyright 2015 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "delegate"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Dns
21
+ class Change
22
+ ##
23
+ # Change::List is a special case Array with additional values.
24
+ class List < DelegateClass(::Array)
25
+ ##
26
+ # If not empty, indicates that there are more changes that match
27
+ # the request and this value should be passed to continue.
28
+ attr_accessor :token
29
+
30
+ ##
31
+ # @private Create a new Change::List with an array of Change
32
+ # instances.
33
+ def initialize arr = []
34
+ super arr
35
+ end
36
+
37
+ ##
38
+ # Whether there a next page of changes.
39
+ #
40
+ # @return [Boolean]
41
+ #
42
+ # @example
43
+ # require "google/cloud"
44
+ #
45
+ # gcloud = Google::Cloud.new
46
+ # dns = gcloud.dns
47
+ # zone = dns.zone "example-com"
48
+ #
49
+ # changes = zone.changes
50
+ # if changes.next?
51
+ # next_changes = changes.next
52
+ # end
53
+ #
54
+ def next?
55
+ !token.nil?
56
+ end
57
+
58
+ ##
59
+ # Retrieve the next page of changes.
60
+ #
61
+ # @return [Change::List]
62
+ #
63
+ # @example
64
+ # require "google/cloud"
65
+ #
66
+ # gcloud = Google::Cloud.new
67
+ # dns = gcloud.dns
68
+ # zone = dns.zone "example-com"
69
+ #
70
+ # changes = zone.changes
71
+ # if changes.next?
72
+ # next_changes = changes.next
73
+ # end
74
+ #
75
+ def next
76
+ return nil unless next?
77
+ ensure_zone!
78
+ @zone.changes token: token, max: @max, order: @order
79
+ end
80
+
81
+ ##
82
+ # Retrieves all changes by repeatedly loading {#next} until {#next?}
83
+ # returns `false`. Calls the given block once for each change, which
84
+ # is passed as the parameter.
85
+ #
86
+ # An Enumerator is returned if no block is given.
87
+ #
88
+ # This method may make several API calls until all changes are
89
+ # retrieved. Be sure to use as narrow a search criteria as possible.
90
+ # Please use with caution.
91
+ #
92
+ # @param [Integer] request_limit The upper limit of API requests to
93
+ # make to load all changes. Default is no limit.
94
+ # @yield [change] The block for accessing each change.
95
+ # @yieldparam [Change] change The change object.
96
+ #
97
+ # @return [Enumerator]
98
+ #
99
+ # @example Iterating each change by passing a block:
100
+ # require "google/cloud"
101
+ #
102
+ # gcloud = Google::Cloud.new
103
+ # dns = gcloud.dns
104
+ # zone = dns.zone "example-com"
105
+ # changes = zone.changes
106
+ #
107
+ # changes.all do |change|
108
+ # puts change.name
109
+ # end
110
+ #
111
+ # @example Using the enumerator by not passing a block:
112
+ # require "google/cloud"
113
+ #
114
+ # gcloud = Google::Cloud.new
115
+ # dns = gcloud.dns
116
+ # zone = dns.zone "example-com"
117
+ # changes = zone.changes
118
+ #
119
+ # all_names = changes.all.map do |change|
120
+ # change.name
121
+ # end
122
+ #
123
+ # @example Limit the number of API calls made:
124
+ # require "google/cloud"
125
+ #
126
+ # gcloud = Google::Cloud.new
127
+ # dns = gcloud.dns
128
+ # zone = dns.zone "example-com"
129
+ # changes = zone.changes
130
+ #
131
+ # changes.all(request_limit: 10) do |change|
132
+ # puts change.name
133
+ # end
134
+ #
135
+ def all request_limit: nil
136
+ request_limit = request_limit.to_i if request_limit
137
+ unless block_given?
138
+ return enum_for(:all, request_limit: request_limit)
139
+ end
140
+ results = self
141
+ loop do
142
+ results.each { |r| yield r }
143
+ if request_limit
144
+ request_limit -= 1
145
+ break if request_limit < 0
146
+ end
147
+ break unless results.next?
148
+ results = results.next
149
+ end
150
+ end
151
+
152
+ ##
153
+ # @private New Changes::List from a response object.
154
+ def self.from_gapi gapi, zone, max = nil, order = nil
155
+ changes = new(Array(gapi.changes).map do |g|
156
+ Change.from_gapi g, zone
157
+ end)
158
+ changes.instance_variable_set "@token", gapi.next_page_token
159
+ changes.instance_variable_set "@zone", zone
160
+ changes.instance_variable_set "@max", max
161
+ changes.instance_variable_set "@order", order
162
+ changes
163
+ end
164
+
165
+ protected
166
+
167
+ ##
168
+ # Raise an error unless an active connection is available.
169
+ def ensure_zone!
170
+ fail "Must have active connection" unless @zone
171
+ end
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright 2015 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/credentials"
17
+
18
+ module Google
19
+ module Cloud
20
+ module Dns
21
+ ##
22
+ # @private Represents the Oauth2 signing logic for DNS.
23
+ class Credentials < Google::Cloud::Credentials
24
+ SCOPE = ["https://www.googleapis.com/auth/ndev.clouddns.readwrite"]
25
+ PATH_ENV_VARS = %w(DNS_KEYFILE GOOGLE_CLOUD_KEYFILE GCLOUD_KEYFILE)
26
+ JSON_ENV_VARS = %w(DNS_KEYFILE_JSON GOOGLE_CLOUD_KEYFILE_JSON
27
+ GCLOUD_KEYFILE_JSON)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,186 @@
1
+ # Copyright 2015 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "zonefile"
17
+ require "google/cloud/dns/record"
18
+
19
+ module Google
20
+ module Cloud
21
+ module Dns
22
+ ##
23
+ # @private
24
+ # # DNS Importer
25
+ #
26
+ # Reads a [DNS zone file](https://en.wikipedia.org/wiki/Zone_file) and
27
+ # parses it, creating a collection of Record instances. The returned
28
+ # records are unsaved, as they are not yet associated with a Zone. Use
29
+ # {Zone#import} to add zone file records to a Zone.
30
+ #
31
+ # Because the Google Cloud DNS API only accepts a single resource record
32
+ # for each `name` and `type` combination (with multiple `data` elements),
33
+ # the zone file's records are merged as necessary. During this merge, the
34
+ # lowest `ttl` of the merged records is used. If none of the merged
35
+ # records have a `ttl` value, the zone file's global TTL is used for the
36
+ # record.
37
+ #
38
+ # The following record types are supported: A, AAAA, CNAME, MX, NAPTR, NS,
39
+ # PTR, SOA, SPF, SRV, and TXT.
40
+ class Importer
41
+ ##
42
+ # Creates a new Importer that immediately parses the provided zone file
43
+ # data and creates Record instances.
44
+ #
45
+ # @param [String, IO] path_or_io The path to a zone file on the
46
+ # filesystem, or an IO instance from which zone file data can be read.
47
+ #
48
+ def initialize zone, path_or_io
49
+ @zone = zone
50
+ @merged_zf_records = {}
51
+ @records = []
52
+ @zonefile = create_zonefile path_or_io
53
+ merge_zonefile_records
54
+ from_zonefile_records
55
+ @records.unshift soa_record
56
+ end
57
+
58
+ ##
59
+ # Returns the Record instances created from the zone file.
60
+ #
61
+ # @param [String, Array<String>] only Include only records of this type
62
+ # or types.
63
+ # @param [String, Array<String>] except Exclude records of this type or
64
+ # types.
65
+ #
66
+ # @return [Array<Record>] An array of unsaved {Record} instances
67
+ #
68
+ def records only: nil, except: nil
69
+ ret = @records
70
+ ret = ret.select { |r| Array(only).include? r.type } if only
71
+ ret = ret.reject { |r| Array(except).include? r.type } if except
72
+ ret
73
+ end
74
+
75
+ protected
76
+
77
+ ##
78
+ # The zonefile library returns a two-element array in which the first
79
+ # element is a symbol type (:a, :mx, and so on), and the second element
80
+ # is an array containing the records of that type. Group the records by
81
+ # name and type instead.
82
+ def merge_zonefile_records
83
+ @zonefile.records.map do |r|
84
+ type = r.first
85
+ type = :aaaa if type == :a4
86
+ r.last.each do |zf_record|
87
+ name = Service.fqdn(zf_record[:name], @zonefile.origin)
88
+ key = [name, type]
89
+ (@merged_zf_records[key] ||= []) << zf_record
90
+ end
91
+ end
92
+ end
93
+
94
+ ##
95
+ # Convert the grouped records to single array of records, merging
96
+ # records of the same name and type into a single record with an array
97
+ # of rrdatas.
98
+ def from_zonefile_records
99
+ @records = @merged_zf_records.map do |key, zf_records|
100
+ ttl = ttl_from_zonefile_records zf_records
101
+ data = zf_records.map do |zf_record|
102
+ data_from_zonefile_record(key[1], zf_record)
103
+ end
104
+ @zone.record key[0], key[1], ttl, data
105
+ end
106
+ end
107
+
108
+ def soa_record
109
+ zf_soa = @zonefile.soa
110
+ ttl = ttl_to_i(zf_soa[:ttl]) || ttl_to_i(@zonefile.ttl)
111
+ data = data_from_zonefile_record :soa, zf_soa
112
+ @zone.record zf_soa[:origin], "SOA", ttl, data
113
+ end
114
+
115
+ ##
116
+ # From a collection of records, take the lowest ttl
117
+ def ttl_from_zonefile_records zf_records
118
+ ttls = zf_records.map do |zf_record|
119
+ ttl_to_i(zf_record[:ttl])
120
+ end
121
+ min_ttl = ttls.compact.sort.first
122
+ min_ttl || ttl_to_i(@zonefile.ttl)
123
+ end
124
+
125
+ # rubocop:disable all
126
+ # Rubocop's line-length and branch condition restrictions prevent
127
+ # the most straightforward approach to converting zonefile's records
128
+ # to our own. So disable rubocop for this operation.
129
+
130
+ def data_from_zonefile_record type, zf_record
131
+ case type.to_s.upcase
132
+ when "A"
133
+ "#{zf_record[:host]}"
134
+ when "AAAA"
135
+ "#{zf_record[:host]}"
136
+ when "CNAME"
137
+ "#{zf_record[:host]}"
138
+ when "MX"
139
+ "#{zf_record[:pri]} #{zf_record[:host]}"
140
+ when "NAPTR"
141
+ "#{zf_record[:order]} #{zf_record[:preference]} #{zf_record[:flags]} #{zf_record[:service]} #{zf_record[:regexp]} #{zf_record[:replacement]}"
142
+ when "NS"
143
+ "#{zf_record[:host]}"
144
+ when "PTR"
145
+ "#{zf_record[:host]}"
146
+ when "SOA"
147
+ "#{zf_record[:primary]} #{zf_record[:email]} #{zf_record[:serial]} #{zf_record[:refresh]} #{zf_record[:retry]} #{zf_record[:expire]} #{zf_record[:minimumTTL]}"
148
+ when "SPF"
149
+ "#{zf_record[:data]}"
150
+ when "SRV"
151
+ "#{zf_record[:pri]} #{zf_record[:weight]} #{zf_record[:port]} #{zf_record[:host]}"
152
+ when "TXT"
153
+ "#{zf_record[:text]}"
154
+ else
155
+ fail ArgumentError, "record type '#{type}' is not supported"
156
+ end
157
+ end
158
+
159
+ # rubocop:enable all
160
+
161
+ MULTIPLIER = { "s" => (1),
162
+ "m" => (60),
163
+ "h" => (60 * 60),
164
+ "d" => (60 * 60 * 24),
165
+ "w" => (60 * 60 * 24 * 7) } # :nodoc:
166
+
167
+ def ttl_to_i ttl
168
+ if ttl.respond_to?(:to_int) || ttl.to_s =~ /\A\d+\z/
169
+ return ttl.to_i
170
+ elsif (m = /\A(\d+)(w|d|h|m|s)\z/.match ttl)
171
+ return m[1].to_i * MULTIPLIER[m[2]].to_i
172
+ end
173
+ nil
174
+ end
175
+
176
+ def create_zonefile path_or_io # :nodoc:
177
+ if path_or_io.respond_to? :read
178
+ Zonefile.new path_or_io.read
179
+ else
180
+ Zonefile.from_file path_or_io
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,251 @@
1
+ # Copyright 2015 Google Inc. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+
16
+ require "google/cloud/errors"
17
+ require "google/cloud/core/gce"
18
+ require "google/cloud/dns/service"
19
+ require "google/cloud/dns/credentials"
20
+ require "google/cloud/dns/zone"
21
+
22
+ module Google
23
+ module Cloud
24
+ module Dns
25
+ ##
26
+ # # Project
27
+ #
28
+ # The project is a top level container for resources including Cloud DNS
29
+ # ManagedZones. Projects can be created only in the [Google Developers
30
+ # Console](https://console.developers.google.com).
31
+ #
32
+ # @example
33
+ # require "google/cloud"
34
+ #
35
+ # gcloud = Google::Cloud.new
36
+ # dns = gcloud.dns
37
+ # zone = dns.zone "example-com"
38
+ # zone.records.each do |record|
39
+ # puts record.name
40
+ # end
41
+ #
42
+ # See {Google::Cloud#dns}
43
+ class Project
44
+ ##
45
+ # @private The Service object.
46
+ attr_accessor :service
47
+
48
+ ##
49
+ # @private The Google API Client object.
50
+ attr_accessor :gapi
51
+
52
+ ##
53
+ # @private Creates a new Service instance.
54
+ #
55
+ # See {Google::Cloud.dns}
56
+ def initialize service
57
+ @service = service
58
+ @gapi = nil
59
+ end
60
+
61
+ ##
62
+ # The unique ID string for the current project.
63
+ #
64
+ # @example
65
+ # require "google/cloud"
66
+ #
67
+ # gcloud = Google::Cloud.new "my-todo-project",
68
+ # "/path/to/keyfile.json"
69
+ # dns = gcloud.dns
70
+ #
71
+ # dns.project #=> "my-todo-project"
72
+ #
73
+ def project
74
+ service.project
75
+ end
76
+ alias_method :id, :project
77
+
78
+ ##
79
+ # The project number.
80
+ def number
81
+ reload! if @gapi.nil?
82
+ @gapi.number
83
+ end
84
+
85
+ ##
86
+ # Maximum allowed number of zones in the project.
87
+ def zones_quota
88
+ reload! if @gapi.nil?
89
+ @gapi.quota.managed_zones if @gapi.quota
90
+ end
91
+
92
+ ##
93
+ # Maximum allowed number of data entries per record.
94
+ def data_per_record
95
+ reload! if @gapi.nil?
96
+ @gapi.quota.resource_records_per_rrset if @gapi.quota
97
+ end
98
+
99
+ ##
100
+ # Maximum allowed number of records to add per change.
101
+ def additions_per_change
102
+ reload! if @gapi.nil?
103
+ @gapi.quota.rrset_additions_per_change if @gapi.quota
104
+ end
105
+
106
+ ##
107
+ # Maximum allowed number of records to delete per change.
108
+ def deletions_per_change
109
+ reload! if @gapi.nil?
110
+ @gapi.quota.rrset_deletions_per_change if @gapi.quota
111
+ end
112
+
113
+ ##
114
+ # Maximum allowed number of records per zone in the project.
115
+ def records_per_zone
116
+ reload! if @gapi.nil?
117
+ @gapi.quota.rrsets_per_managed_zone if @gapi.quota
118
+ end
119
+
120
+ ##
121
+ # Maximum allowed total bytes size for all the data in one change.
122
+ def total_data_per_change
123
+ reload! if @gapi.nil?
124
+ @gapi.quota.total_rrdata_size_per_change if @gapi.quota
125
+ end
126
+
127
+ ##
128
+ # @private Default project.
129
+ def self.default_project
130
+ ENV["DNS_PROJECT"] ||
131
+ ENV["GOOGLE_CLOUD_PROJECT"] ||
132
+ ENV["GCLOUD_PROJECT"] ||
133
+ Google::Cloud::Core::GCE.project_id
134
+ end
135
+
136
+ ##
137
+ # Retrieves an existing zone by name or id.
138
+ #
139
+ # @param [String, Integer] zone_id The name or id of a zone.
140
+ #
141
+ # @return [Google::Cloud::Dns::Zone, nil] Returns `nil` if the zone does
142
+ # not exist.
143
+ #
144
+ # @example
145
+ # require "google/cloud"
146
+ #
147
+ # gcloud = Google::Cloud.new
148
+ # dns = gcloud.dns
149
+ # zone = dns.zone "example-com"
150
+ # puts zone.name
151
+ #
152
+ def zone zone_id
153
+ ensure_service!
154
+ gapi = service.get_zone zone_id
155
+ Zone.from_gapi gapi, service
156
+ rescue Google::Cloud::NotFoundError
157
+ nil
158
+ end
159
+ alias_method :find_zone, :zone
160
+ alias_method :get_zone, :zone
161
+
162
+ ##
163
+ # Retrieves the list of zones belonging to the project.
164
+ #
165
+ # @param [String] token A previously-returned page token representing
166
+ # part of the larger set of results to view.
167
+ # @param [Integer] max Maximum number of zones to return.
168
+ #
169
+ # @return [Array<Google::Cloud::Dns::Zone>] (See
170
+ # {Google::Cloud::Dns::Zone::List})
171
+ #
172
+ # @example
173
+ # require "google/cloud"
174
+ #
175
+ # gcloud = Google::Cloud.new
176
+ # dns = gcloud.dns
177
+ # zones = dns.zones
178
+ # zones.each do |zone|
179
+ # puts zone.name
180
+ # end
181
+ #
182
+ # @example Retrieve all zones: (See {Zone::List#all})
183
+ # require "google/cloud"
184
+ #
185
+ # gcloud = Google::Cloud.new
186
+ # dns = gcloud.dns
187
+ # zones = dns.zones
188
+ # zones.all do |zone|
189
+ # puts zone.name
190
+ # end
191
+ #
192
+ def zones token: nil, max: nil
193
+ ensure_service!
194
+ gapi = service.list_zones token: token, max: max
195
+ Zone::List.from_gapi gapi, service, max
196
+ end
197
+ alias_method :find_zones, :zones
198
+
199
+ ##
200
+ # Creates a new zone.
201
+ #
202
+ # @param [String] zone_name User assigned name for this resource. Must
203
+ # be unique within the project. The name must be 1-32 characters long,
204
+ # must begin with a letter, end with a letter or digit, and only
205
+ # contain lowercase letters, digits or dashes.
206
+ # @param [String] zone_dns The DNS name of this managed zone, for
207
+ # instance "example.com.".
208
+ # @param [String] description A string of at most 1024 characters
209
+ # associated with this resource for the user's convenience. Has no
210
+ # effect on the managed zone's function.
211
+ # @param [String] name_server_set A NameServerSet is a set of DNS name
212
+ # servers that all host the same ManagedZones. Most users will leave
213
+ # this field unset.
214
+ #
215
+ # @return [Google::Cloud::Dns::Zone]
216
+ #
217
+ # @example
218
+ # require "google/cloud"
219
+ #
220
+ # gcloud = Google::Cloud.new
221
+ # dns = gcloud.dns
222
+ # zone = dns.create_zone "example-com", "example.com."
223
+ #
224
+ def create_zone zone_name, zone_dns, description: nil,
225
+ name_server_set: nil
226
+ ensure_service!
227
+ gapi = service.create_zone zone_name, zone_dns,
228
+ description: description,
229
+ name_server_set: name_server_set
230
+ Zone.from_gapi gapi, service
231
+ end
232
+
233
+ ##
234
+ # Reloads the change with updated status from the DNS service.
235
+ def reload!
236
+ ensure_service!
237
+ @gapi = service.get_project
238
+ end
239
+ alias_method :refresh!, :reload!
240
+
241
+ protected
242
+
243
+ ##
244
+ # Raise an error unless an active connection is available.
245
+ def ensure_service!
246
+ fail "Must have active connection" unless service
247
+ end
248
+ end
249
+ end
250
+ end
251
+ end