google-cloud-dns 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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