gcloud 0.3.1 → 0.4.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.
- 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
|