gcloud 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +8 -8
- data/CHANGELOG.md +12 -0
- data/OVERVIEW.md +28 -0
- data/lib/gcloud.rb +46 -0
- data/lib/gcloud/bigquery.rb +15 -6
- data/lib/gcloud/bigquery/connection.rb +25 -12
- data/lib/gcloud/bigquery/table.rb +74 -7
- data/lib/gcloud/dns.rb +280 -0
- data/lib/gcloud/dns/change.rb +163 -0
- data/lib/gcloud/dns/change/list.rb +70 -0
- data/lib/gcloud/dns/connection.rb +164 -0
- data/lib/gcloud/dns/credentials.rb +29 -0
- data/lib/gcloud/dns/errors.rb +64 -0
- data/lib/gcloud/dns/importer.rb +195 -0
- data/lib/gcloud/dns/project.rb +291 -0
- data/lib/gcloud/dns/record.rb +152 -0
- data/lib/gcloud/dns/record/list.rb +92 -0
- data/lib/gcloud/dns/zone.rb +924 -0
- data/lib/gcloud/dns/zone/list.rb +75 -0
- data/lib/gcloud/dns/zone/transaction.rb +192 -0
- data/lib/gcloud/storage.rb +15 -6
- data/lib/gcloud/storage/bucket.rb +16 -7
- data/lib/gcloud/version.rb +1 -1
- metadata +29 -2
@@ -0,0 +1,195 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2015 Google Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require "zonefile"
|
17
|
+
require "gcloud/dns/record"
|
18
|
+
|
19
|
+
module Gcloud
|
20
|
+
module Dns
|
21
|
+
##
|
22
|
+
# = DNS Importer
|
23
|
+
#
|
24
|
+
# Reads a {DNS zone
|
25
|
+
# file}[https://en.wikipedia.org/wiki/Zone_file] and parses it, creating a
|
26
|
+
# collection of Record instances. The returned records are unsaved,
|
27
|
+
# as they are not yet associated with a Zone. Use Zone#import to add zone
|
28
|
+
# file records to a Zone.
|
29
|
+
#
|
30
|
+
# Because the Google Cloud DNS API only accepts a single resource record for
|
31
|
+
# each +name+ and +type+ combination (with multiple +data+ elements), the
|
32
|
+
# zone file's records are merged as necessary. During this merge, the lowest
|
33
|
+
# +ttl+ of the merged records is used. If none of the merged records have a
|
34
|
+
# +ttl+ value, the zone file's global TTL is used for the record.
|
35
|
+
#
|
36
|
+
# The following record types are supported: A, AAAA, CNAME, MX, NAPTR, NS,
|
37
|
+
# PTR, SOA, SPF, SRV, and TXT.
|
38
|
+
class Importer #:nodoc:
|
39
|
+
##
|
40
|
+
# Creates a new Importer that immediately parses the provided zone file
|
41
|
+
# data and creates Record instances.
|
42
|
+
#
|
43
|
+
# === Parameters
|
44
|
+
#
|
45
|
+
# +path_or_io+::
|
46
|
+
# The path to a zone file on the filesystem, or an IO instance from
|
47
|
+
# which zone file data can be read. (+String+ or +IO+)
|
48
|
+
#
|
49
|
+
def initialize zone, path_or_io
|
50
|
+
@zone = zone
|
51
|
+
@merged_zf_records = {}
|
52
|
+
@records = []
|
53
|
+
@zonefile = create_zonefile path_or_io
|
54
|
+
merge_zonefile_records
|
55
|
+
from_zonefile_records
|
56
|
+
@records.unshift soa_record
|
57
|
+
end
|
58
|
+
|
59
|
+
##
|
60
|
+
# Returns the Record instances created from the zone file.
|
61
|
+
#
|
62
|
+
# === Parameters
|
63
|
+
#
|
64
|
+
# +options+::
|
65
|
+
# An optional Hash for controlling additional behavior. (+Hash+)
|
66
|
+
# <code>options[:only]</code>::
|
67
|
+
# Include only records of this type or types. (+String+ or +Array+)
|
68
|
+
# <code>options[:except]</code>::
|
69
|
+
# Exclude records of this type or types. (+String+ or +Array+)
|
70
|
+
#
|
71
|
+
# === Returns
|
72
|
+
#
|
73
|
+
# An array of unsaved Record instances.
|
74
|
+
#
|
75
|
+
def records options = {}
|
76
|
+
filtered_records options[:only], options[:except]
|
77
|
+
end
|
78
|
+
|
79
|
+
protected
|
80
|
+
|
81
|
+
def filtered_records only, except
|
82
|
+
ret = @records
|
83
|
+
ret = ret.select { |r| Array(only).include? r.type } if only
|
84
|
+
ret = ret.reject { |r| Array(except).include? r.type } if except
|
85
|
+
ret
|
86
|
+
end
|
87
|
+
|
88
|
+
##
|
89
|
+
# The zonefile library returns a two-element array in which the first
|
90
|
+
# element is a symbol type (:a, :mx, and so on), and the second element
|
91
|
+
# is an array containing the records of that type. Group the records by
|
92
|
+
# name and type instead.
|
93
|
+
def merge_zonefile_records
|
94
|
+
@zonefile.records.map do |r|
|
95
|
+
type = r.first
|
96
|
+
type = :aaaa if type == :a4
|
97
|
+
r.last.each do |zf_record|
|
98
|
+
name = Connection.fqdn(zf_record[:name], @zonefile.origin)
|
99
|
+
key = [name, type]
|
100
|
+
(@merged_zf_records[key] ||= []) << zf_record
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
##
|
106
|
+
# Convert the grouped records to single array of records, merging records
|
107
|
+
# of the same name and type into a single record with an array of rrdatas.
|
108
|
+
def from_zonefile_records
|
109
|
+
@records = @merged_zf_records.map do |key, zf_records|
|
110
|
+
ttl = ttl_from_zonefile_records zf_records
|
111
|
+
data = zf_records.map do |zf_record|
|
112
|
+
data_from_zonefile_record(key[1], zf_record)
|
113
|
+
end
|
114
|
+
@zone.record key[0], key[1], ttl, data
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def soa_record
|
119
|
+
zf_soa = @zonefile.soa
|
120
|
+
ttl = ttl_to_i(zf_soa[:ttl]) || ttl_to_i(@zonefile.ttl)
|
121
|
+
data = data_from_zonefile_record :soa, zf_soa
|
122
|
+
@zone.record zf_soa[:origin], "SOA", ttl, data
|
123
|
+
end
|
124
|
+
|
125
|
+
##
|
126
|
+
# From a collection of records, take the lowest ttl
|
127
|
+
def ttl_from_zonefile_records zf_records
|
128
|
+
ttls = zf_records.map do |zf_record|
|
129
|
+
ttl_to_i(zf_record[:ttl])
|
130
|
+
end
|
131
|
+
min_ttl = ttls.compact.sort.first
|
132
|
+
min_ttl || ttl_to_i(@zonefile.ttl)
|
133
|
+
end
|
134
|
+
|
135
|
+
# rubocop:disable all
|
136
|
+
# Rubocop's line-length and branch condition restrictions prevent
|
137
|
+
# the most straightforward approach to converting zonefile's records
|
138
|
+
# to our own. So disable rubocop for this operation.
|
139
|
+
|
140
|
+
def data_from_zonefile_record type, zf_record
|
141
|
+
case type.to_s.upcase
|
142
|
+
when "A"
|
143
|
+
"#{zf_record[:host]}"
|
144
|
+
when "AAAA"
|
145
|
+
"#{zf_record[:host]}"
|
146
|
+
when "CNAME"
|
147
|
+
"#{zf_record[:host]}"
|
148
|
+
when "MX"
|
149
|
+
"#{zf_record[:pri]} #{zf_record[:host]}"
|
150
|
+
when "NAPTR"
|
151
|
+
"#{zf_record[:order]} #{zf_record[:preference]} #{zf_record[:flags]} #{zf_record[:service]} #{zf_record[:regexp]} #{zf_record[:replacement]}"
|
152
|
+
when "NS"
|
153
|
+
"#{zf_record[:host]}"
|
154
|
+
when "PTR"
|
155
|
+
"#{zf_record[:host]}"
|
156
|
+
when "SOA"
|
157
|
+
"#{zf_record[:primary]} #{zf_record[:email]} #{zf_record[:serial]} #{zf_record[:refresh]} #{zf_record[:retry]} #{zf_record[:expire]} #{zf_record[:minimumTTL]}"
|
158
|
+
when "SPF"
|
159
|
+
"#{zf_record[:data]}"
|
160
|
+
when "SRV"
|
161
|
+
"#{zf_record[:pri]} #{zf_record[:weight]} #{zf_record[:port]} #{zf_record[:host]}"
|
162
|
+
when "TXT"
|
163
|
+
"#{zf_record[:text]}"
|
164
|
+
else
|
165
|
+
fail ArgumentError, "record type '#{type}' is not supported"
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# rubocop:enable all
|
170
|
+
|
171
|
+
MULTIPLIER = { "s" => (1),
|
172
|
+
"m" => (60),
|
173
|
+
"h" => (60 * 60),
|
174
|
+
"d" => (60 * 60 * 24),
|
175
|
+
"w" => (60 * 60 * 24 * 7) } # :nodoc:
|
176
|
+
|
177
|
+
def ttl_to_i ttl
|
178
|
+
if ttl.respond_to?(:to_int) || ttl.to_s =~ /\A\d+\z/
|
179
|
+
return ttl.to_i
|
180
|
+
elsif (m = /\A(\d+)(w|d|h|m|s)\z/.match ttl)
|
181
|
+
return m[1].to_i * MULTIPLIER[m[2]].to_i
|
182
|
+
end
|
183
|
+
nil
|
184
|
+
end
|
185
|
+
|
186
|
+
def create_zonefile path_or_io # :nodoc:
|
187
|
+
if path_or_io.respond_to? :read
|
188
|
+
Zonefile.new path_or_io.read
|
189
|
+
else
|
190
|
+
Zonefile.from_file path_or_io
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
@@ -0,0 +1,291 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright 2015 Google Inc. All rights reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
|
16
|
+
require "gcloud/gce"
|
17
|
+
require "gcloud/dns/connection"
|
18
|
+
require "gcloud/dns/credentials"
|
19
|
+
require "gcloud/dns/zone"
|
20
|
+
require "gcloud/dns/errors"
|
21
|
+
|
22
|
+
module Gcloud
|
23
|
+
module Dns
|
24
|
+
##
|
25
|
+
# = Project
|
26
|
+
#
|
27
|
+
# The project is a top level container for resources including Cloud DNS
|
28
|
+
# ManagedZones. Projects can be created only in the {Google Developers
|
29
|
+
# Console}[https://console.developers.google.com].
|
30
|
+
#
|
31
|
+
# require "gcloud"
|
32
|
+
#
|
33
|
+
# gcloud = Gcloud.new
|
34
|
+
# dns = gcloud.dns
|
35
|
+
# zone = dns.zone "example-com"
|
36
|
+
# zone.records.each do |record|
|
37
|
+
# puts record.name
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# See Gcloud#dns
|
41
|
+
class Project
|
42
|
+
##
|
43
|
+
# The Connection object.
|
44
|
+
attr_accessor :connection #:nodoc:
|
45
|
+
|
46
|
+
##
|
47
|
+
# The Google API Client object.
|
48
|
+
attr_accessor :gapi #:nodoc:
|
49
|
+
|
50
|
+
##
|
51
|
+
# Creates a new Connection instance.
|
52
|
+
#
|
53
|
+
# See Gcloud.dns
|
54
|
+
def initialize project, credentials #:nodoc:
|
55
|
+
project = project.to_s # Always cast to a string
|
56
|
+
fail ArgumentError, "project is missing" if project.empty?
|
57
|
+
@connection = Connection.new project, credentials
|
58
|
+
@gapi = nil
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# The unique ID string for the current project.
|
63
|
+
#
|
64
|
+
# === Example
|
65
|
+
#
|
66
|
+
# require "gcloud"
|
67
|
+
#
|
68
|
+
# gcloud = Gcloud.new "my-todo-project", "/path/to/keyfile.json"
|
69
|
+
# dns = gcloud.dns
|
70
|
+
#
|
71
|
+
# dns.project #=> "my-todo-project"
|
72
|
+
#
|
73
|
+
def project
|
74
|
+
connection.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"]["managedZones"] 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"]["resourceRecordsPerRrset"] 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"]["rrsetAdditionsPerChange"] 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"]["rrsetDeletionsPerChange"] 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"]["rrsetsPerManagedZone"] 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"]["totalRrdataSizePerChange"] if @gapi["quota"]
|
125
|
+
end
|
126
|
+
|
127
|
+
##
|
128
|
+
# Default project.
|
129
|
+
def self.default_project #:nodoc:
|
130
|
+
ENV["DNS_PROJECT"] ||
|
131
|
+
ENV["GCLOUD_PROJECT"] ||
|
132
|
+
ENV["GOOGLE_CLOUD_PROJECT"] ||
|
133
|
+
Gcloud::GCE.project_id
|
134
|
+
end
|
135
|
+
|
136
|
+
##
|
137
|
+
# Retrieves an existing zone by name or id.
|
138
|
+
#
|
139
|
+
# === Parameters
|
140
|
+
#
|
141
|
+
# +zone_id+::
|
142
|
+
# The name or id of a zone. (+String+ or +Integer+)
|
143
|
+
#
|
144
|
+
# === Returns
|
145
|
+
#
|
146
|
+
# Gcloud::Dns::Zone or +nil+ if the zone does not exist
|
147
|
+
#
|
148
|
+
# === Example
|
149
|
+
#
|
150
|
+
# require "gcloud"
|
151
|
+
#
|
152
|
+
# gcloud = Gcloud.new
|
153
|
+
# dns = gcloud.dns
|
154
|
+
# zone = dns.zone "example-com"
|
155
|
+
# puts zone.name
|
156
|
+
#
|
157
|
+
def zone zone_id
|
158
|
+
ensure_connection!
|
159
|
+
resp = connection.get_zone zone_id
|
160
|
+
if resp.success?
|
161
|
+
Zone.from_gapi resp.data, connection
|
162
|
+
else
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
end
|
166
|
+
alias_method :find_zone, :zone
|
167
|
+
alias_method :get_zone, :zone
|
168
|
+
|
169
|
+
##
|
170
|
+
# Retrieves the list of zones belonging to the project.
|
171
|
+
#
|
172
|
+
# === Parameters
|
173
|
+
#
|
174
|
+
# +options+::
|
175
|
+
# An optional Hash for controlling additional behavior. (+Hash+)
|
176
|
+
# <code>options[:token]</code>::
|
177
|
+
# A previously-returned page token representing part of the larger set
|
178
|
+
# of results to view. (+String+)
|
179
|
+
# <code>options[:max]</code>::
|
180
|
+
# Maximum number of zones to return. (+Integer+)
|
181
|
+
#
|
182
|
+
# === Returns
|
183
|
+
#
|
184
|
+
# Array of Gcloud::Dns::Zone (Gcloud::Dns::Zone::List)
|
185
|
+
#
|
186
|
+
# === Examples
|
187
|
+
#
|
188
|
+
# require "gcloud"
|
189
|
+
#
|
190
|
+
# gcloud = Gcloud.new
|
191
|
+
# dns = gcloud.dns
|
192
|
+
# zones = dns.zones
|
193
|
+
# zones.each do |zone|
|
194
|
+
# puts zone.name
|
195
|
+
# end
|
196
|
+
#
|
197
|
+
# If you have a significant number of zones, you may need to paginate
|
198
|
+
# through them: (See Gcloud::Dns::Zone::List)
|
199
|
+
#
|
200
|
+
# require "gcloud"
|
201
|
+
#
|
202
|
+
# gcloud = Gcloud.new
|
203
|
+
# dns = gcloud.dns
|
204
|
+
# zones = dns.zones
|
205
|
+
# loop do
|
206
|
+
# zones.each do |zone|
|
207
|
+
# puts zone.name
|
208
|
+
# end
|
209
|
+
# break unless zones.next?
|
210
|
+
# zones = zones.next
|
211
|
+
# end
|
212
|
+
#
|
213
|
+
def zones options = {}
|
214
|
+
ensure_connection!
|
215
|
+
resp = connection.list_zones options
|
216
|
+
if resp.success?
|
217
|
+
Zone::List.from_response resp, connection
|
218
|
+
else
|
219
|
+
fail ApiError.from_response(resp)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
alias_method :find_zones, :zones
|
223
|
+
|
224
|
+
##
|
225
|
+
# Creates a new zone.
|
226
|
+
#
|
227
|
+
# === Parameters
|
228
|
+
#
|
229
|
+
# +zone_name+::
|
230
|
+
# User assigned name for this resource. Must be unique within the
|
231
|
+
# project. The name must be 1-32 characters long, must begin with a
|
232
|
+
# letter, end with a letter or digit, and only contain lowercase
|
233
|
+
# letters, digits or dashes. (+String+)
|
234
|
+
# +zone_dns+::
|
235
|
+
# The DNS name of this managed zone, for instance "example.com.".
|
236
|
+
# (+String+)
|
237
|
+
# +options+::
|
238
|
+
# An optional Hash for controlling additional behavior. (+Hash+)
|
239
|
+
# <code>options[:description]</code>::
|
240
|
+
# A string of at most 1024 characters associated with this resource for
|
241
|
+
# the user's convenience. Has no effect on the managed zone's function.
|
242
|
+
# (+String+)
|
243
|
+
# <code>options[:name_server_set]</code>::
|
244
|
+
# A NameServerSet is a set of DNS name servers that all host the same
|
245
|
+
# ManagedZones. Most users will leave this field unset. (+String+)
|
246
|
+
#
|
247
|
+
# === Returns
|
248
|
+
#
|
249
|
+
# Gcloud::Dns::Zone
|
250
|
+
#
|
251
|
+
# === Examples
|
252
|
+
#
|
253
|
+
# require "gcloud"
|
254
|
+
#
|
255
|
+
# gcloud = Gcloud.new
|
256
|
+
# dns = gcloud.dns
|
257
|
+
# zone = dns.create_zone "example-com", "example.com."
|
258
|
+
#
|
259
|
+
def create_zone zone_name, zone_dns, options = {}
|
260
|
+
ensure_connection!
|
261
|
+
resp = connection.create_zone zone_name, zone_dns, options
|
262
|
+
if resp.success?
|
263
|
+
Zone.from_gapi resp.data, connection
|
264
|
+
else
|
265
|
+
fail ApiError.from_response(resp)
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
##
|
270
|
+
# Reloads the change with updated status from the DNS service.
|
271
|
+
def reload!
|
272
|
+
ensure_connection!
|
273
|
+
resp = connection.get_project
|
274
|
+
if resp.success?
|
275
|
+
@gapi = resp.data
|
276
|
+
else
|
277
|
+
fail ApiError.from_response(resp)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
alias_method :refresh!, :reload!
|
281
|
+
|
282
|
+
protected
|
283
|
+
|
284
|
+
##
|
285
|
+
# Raise an error unless an active connection is available.
|
286
|
+
def ensure_connection!
|
287
|
+
fail "Must have active connection" unless connection
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|