gclouder 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +104 -0
- data/bin/gclouder +7 -0
- data/lib/gclouder/config/arguments.rb +35 -0
- data/lib/gclouder/config/cli_args.rb +77 -0
- data/lib/gclouder/config/cluster.rb +66 -0
- data/lib/gclouder/config/defaults.rb +35 -0
- data/lib/gclouder/config/files/project.rb +24 -0
- data/lib/gclouder/config/project.rb +34 -0
- data/lib/gclouder/config/resource_representations.rb +31 -0
- data/lib/gclouder/config_loader.rb +25 -0
- data/lib/gclouder/config_section.rb +26 -0
- data/lib/gclouder/dependencies.rb +11 -0
- data/lib/gclouder/gcloud.rb +39 -0
- data/lib/gclouder/gsutil.rb +25 -0
- data/lib/gclouder/header.rb +9 -0
- data/lib/gclouder/helpers.rb +77 -0
- data/lib/gclouder/logging.rb +181 -0
- data/lib/gclouder/mappings/argument.rb +31 -0
- data/lib/gclouder/mappings/file.rb +31 -0
- data/lib/gclouder/mappings/property.rb +31 -0
- data/lib/gclouder/mappings/resource_representation.rb +31 -0
- data/lib/gclouder/monkey_patches/array.rb +19 -0
- data/lib/gclouder/monkey_patches/boolean.rb +12 -0
- data/lib/gclouder/monkey_patches/hash.rb +44 -0
- data/lib/gclouder/monkey_patches/ipaddr.rb +10 -0
- data/lib/gclouder/monkey_patches/string.rb +30 -0
- data/lib/gclouder/resource.rb +63 -0
- data/lib/gclouder/resource_cleaner.rb +58 -0
- data/lib/gclouder/resources/compute/addresses.rb +108 -0
- data/lib/gclouder/resources/compute/bgp-vpns.rb +220 -0
- data/lib/gclouder/resources/compute/disks.rb +99 -0
- data/lib/gclouder/resources/compute/firewall_rules.rb +82 -0
- data/lib/gclouder/resources/compute/instances.rb +147 -0
- data/lib/gclouder/resources/compute/networks/subnets.rb +104 -0
- data/lib/gclouder/resources/compute/networks.rb +110 -0
- data/lib/gclouder/resources/compute/project_info/ssh_keys.rb +171 -0
- data/lib/gclouder/resources/compute/routers.rb +83 -0
- data/lib/gclouder/resources/compute/vpns.rb +199 -0
- data/lib/gclouder/resources/container/clusters.rb +257 -0
- data/lib/gclouder/resources/container/node_pools.rb +193 -0
- data/lib/gclouder/resources/dns.rb +390 -0
- data/lib/gclouder/resources/logging/sinks.rb +98 -0
- data/lib/gclouder/resources/project/iam_policy_binding.rb +293 -0
- data/lib/gclouder/resources/project.rb +85 -0
- data/lib/gclouder/resources/project_id.rb +71 -0
- data/lib/gclouder/resources/pubsub/subscriptions.rb +100 -0
- data/lib/gclouder/resources/pubsub/topics.rb +95 -0
- data/lib/gclouder/resources/storagebuckets.rb +103 -0
- data/lib/gclouder/resources/validate/global.rb +27 -0
- data/lib/gclouder/resources/validate/local.rb +68 -0
- data/lib/gclouder/resources/validate/region.rb +28 -0
- data/lib/gclouder/resources/validate/remote.rb +78 -0
- data/lib/gclouder/resources.rb +148 -0
- data/lib/gclouder/shell.rb +71 -0
- data/lib/gclouder/version.rb +5 -0
- data/lib/gclouder.rb +278 -0
- metadata +102 -0
@@ -0,0 +1,390 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module GClouder
|
4
|
+
module Resources
|
5
|
+
module DNS
|
6
|
+
include GClouder::GCloud
|
7
|
+
include GClouder::Config::Project
|
8
|
+
include GClouder::Config::CLIArgs
|
9
|
+
include GClouder::Logging
|
10
|
+
|
11
|
+
def self.clean
|
12
|
+
return if undefined.empty?
|
13
|
+
header :clean
|
14
|
+
|
15
|
+
undefined.each do |region, zones|
|
16
|
+
info region, indent: 2, heading: true
|
17
|
+
zones.each do |zone|
|
18
|
+
next unless zone.key?("records")
|
19
|
+
info zone["name"], indent: 3, heading: true
|
20
|
+
zone["records"].each do |record|
|
21
|
+
warning "#{record['name']} IN A #{record['type']} #{record['ttl']} #{record['rrdatas'].join(' ')}", indent: 4
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.update_zone_record(zones, zone, record, key, value)
|
28
|
+
record = zones.fetch_with_default("name", zone, {}).fetch("records", []).fetch_with_default("name", record, {})
|
29
|
+
fatal "couldn't update zone record" if record.empty?
|
30
|
+
record[key] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.record?(zones, zone, record)
|
34
|
+
zones.fetch_with_default("name", zone, {}).fetch("records", []).fetch_with_default("name", record, {}).empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.zone?(zones, zone)
|
38
|
+
zones.fetch_with_default("name", zone, {}).empty?
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.zone_record?(zones, zone_name, record_name)
|
42
|
+
found_zone = zones.find { |z| z["name"] == zone_name }
|
43
|
+
return unless found_zone
|
44
|
+
found["records"].find { |r| r["name"] == record_name }.nil?
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.zone_records_append(zones, zone, record)
|
48
|
+
zone = zones.fetch_with_default("name", zone, {})
|
49
|
+
fatal "couldn't update zone" if zone.empty?
|
50
|
+
zone["records"] << record
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.undefined
|
54
|
+
return {} if Remote.list.empty?
|
55
|
+
|
56
|
+
Remote.list.each_with_object({ "global" => [] }) do |(_region, zones), collection|
|
57
|
+
zones.each do |zone|
|
58
|
+
# if zone isnt defined locally, then add it along with its associated records
|
59
|
+
if !zone?(zones, zone["name"])
|
60
|
+
collection["global"] << zone
|
61
|
+
next
|
62
|
+
end
|
63
|
+
|
64
|
+
next unless zone.key?("records")
|
65
|
+
|
66
|
+
# if record isnt defined locally, create a zone in global (if one doesn't exist), then append record to records field
|
67
|
+
zone["records"].each do |record|
|
68
|
+
if !zone?(collection["global"], zone["name"])
|
69
|
+
zone_collection = zone.dup
|
70
|
+
zone_collection["records"] = []
|
71
|
+
collection["global"] << zone_collection
|
72
|
+
end
|
73
|
+
|
74
|
+
if !record?(zones, zone["name"], record["name"])
|
75
|
+
zone_records_append(collection["global"], zone["name"], record)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.validate
|
83
|
+
return if Local.list.empty?
|
84
|
+
header :validate
|
85
|
+
|
86
|
+
failure = false
|
87
|
+
|
88
|
+
Local.list.each do |region, zones|
|
89
|
+
info region, indent: 2, heading: true
|
90
|
+
|
91
|
+
unless zones.is_a?(Array)
|
92
|
+
failure = true
|
93
|
+
bad "zones value should be an array", indent: 3, heading: true
|
94
|
+
next
|
95
|
+
end
|
96
|
+
|
97
|
+
zones.each do |zone|
|
98
|
+
unless zone.is_a?(Hash)
|
99
|
+
failure = true
|
100
|
+
bad "zone value should be a hash", indent: 3, heading: true
|
101
|
+
next
|
102
|
+
end
|
103
|
+
|
104
|
+
unless zone.key?("name")
|
105
|
+
failure = true
|
106
|
+
bad "zone with missing key: name", indent: 3, heading: true
|
107
|
+
next
|
108
|
+
end
|
109
|
+
|
110
|
+
if zone["name"] !~ /^[a-z0-9\-]+$/
|
111
|
+
failure = true
|
112
|
+
bad "zone name must only contain lower-case letters, digits or dashes"
|
113
|
+
next
|
114
|
+
end
|
115
|
+
|
116
|
+
info zone["name"], indent: 3, heading: true
|
117
|
+
|
118
|
+
if zone.key?("zone")
|
119
|
+
good "resource has zone specified (#{zone['zone']})", indent: 4
|
120
|
+
else
|
121
|
+
failure = true
|
122
|
+
bad "missing key: zone", indent: 4
|
123
|
+
end
|
124
|
+
|
125
|
+
next unless zone.key?("records")
|
126
|
+
|
127
|
+
zone["records"].each do |record|
|
128
|
+
info record["name"], indent: 4, heading: true
|
129
|
+
if ["A", "CNAME", "PTR", "NS", "TXT"].include?(record["type"])
|
130
|
+
good "record has valid type (#{record['type']})", indent: 5
|
131
|
+
else
|
132
|
+
bad "unknown record type: #{record['type']}", indent: 5
|
133
|
+
failure = true
|
134
|
+
end
|
135
|
+
|
136
|
+
if record["ttl"].is_a?(Integer)
|
137
|
+
good "record has valid ttl (#{record['ttl']})", indent: 5
|
138
|
+
else
|
139
|
+
bad "record has invalid ttl: #{record['ttl']}", indent: 5
|
140
|
+
failure = true
|
141
|
+
end
|
142
|
+
|
143
|
+
if record.key?("value") || record.key?("static_ips")
|
144
|
+
good "record has a target", indent: 5
|
145
|
+
else
|
146
|
+
bad "record has no target", indent: 5
|
147
|
+
failure = true
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
fatal "failure due to invalid config" if failure
|
154
|
+
end
|
155
|
+
|
156
|
+
def self.header(stage = :ensure)
|
157
|
+
info "[#{stage}] dns", indent: 1, title: true
|
158
|
+
end
|
159
|
+
|
160
|
+
def self.ensure
|
161
|
+
return if Local.list.empty?
|
162
|
+
|
163
|
+
header
|
164
|
+
|
165
|
+
Local.list.each do |region, zones|
|
166
|
+
info region, heading: true, indent: 2
|
167
|
+
|
168
|
+
zones.each do |zone|
|
169
|
+
project_id = zone_project_id(zone)
|
170
|
+
|
171
|
+
next if skip?(project_id, zone)
|
172
|
+
|
173
|
+
info
|
174
|
+
Zone.ensure(project_id, zone["name"], zone["zone"])
|
175
|
+
Records.ensure(project_id, zone)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def self.skip?(project_id, zone)
|
181
|
+
return false if project_id == project["project_id"]
|
182
|
+
return false if !cli_args[:skip_cross_project_resources]
|
183
|
+
|
184
|
+
extra_info = " [#{project_id}]" if project_id != project["project_id"]
|
185
|
+
warning "#{zone['name']}#{extra_info} [skipping] (cross project resource)", indent: 3, heading: true
|
186
|
+
true
|
187
|
+
end
|
188
|
+
|
189
|
+
def self.zone_project_id(zone_config)
|
190
|
+
return project["project_id"] unless zone_config
|
191
|
+
zone_config.key?("project_id") ? zone_config["project_id"] : project["project_id"]
|
192
|
+
end
|
193
|
+
|
194
|
+
module Zone
|
195
|
+
include GClouder::Config::Project
|
196
|
+
|
197
|
+
def self.ensure(project_id, name, zone)
|
198
|
+
extra_info = (project_id != project["project_id"]) ? "[#{project_id}]" : ""
|
199
|
+
|
200
|
+
Resource.ensure :"dns managed-zones", name,
|
201
|
+
"--dns-name=#{zone} --description='Created by GClouder'", project_id: project_id, extra_info: extra_info
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
module Records
|
206
|
+
include GClouder::Logging
|
207
|
+
include GClouder::Config::CLIArgs
|
208
|
+
include GClouder::GCloud
|
209
|
+
|
210
|
+
def self.ensure(project_id, zone)
|
211
|
+
return unless zone.key?("records")
|
212
|
+
|
213
|
+
start_transaction(project_id, zone["name"])
|
214
|
+
|
215
|
+
zone["records"].each do |record|
|
216
|
+
next unless record_is_valid(record)
|
217
|
+
|
218
|
+
values = []
|
219
|
+
|
220
|
+
if record.key?("value") && record["value"].is_a?(Array)
|
221
|
+
values << record["value"].join(" ")
|
222
|
+
|
223
|
+
elsif record.key?("value") && record["value"].is_a?(String)
|
224
|
+
values << record["value"]
|
225
|
+
|
226
|
+
elsif record.key?("static_ips")
|
227
|
+
record["static_ips"].each do |ip|
|
228
|
+
values << static_ip(project_id, zone["name"], ip)
|
229
|
+
end
|
230
|
+
|
231
|
+
else
|
232
|
+
bad "no 'value' or 'static_ips' key found for record: #{record["name"]}"
|
233
|
+
fatal "failure due to invalid config"
|
234
|
+
end
|
235
|
+
|
236
|
+
values.each do |value|
|
237
|
+
unless record["name"].match(/\.$/)
|
238
|
+
bad "record name missing '.' suffix: #{record["name"]}"
|
239
|
+
fatal "failure due to invalid config"
|
240
|
+
end
|
241
|
+
ttl = record.key?("ttl") ? record["ttl"] : "300"
|
242
|
+
add_record_set record["name"], value, zone["name"], record["type"], ttl, project_id
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
execute_transaction(project_id, zone["name"])
|
247
|
+
end
|
248
|
+
|
249
|
+
def self.start_transaction(project_id, zone_name)
|
250
|
+
gcloud "dns record-sets transaction start --zone=#{zone_name}", project_id: project_id
|
251
|
+
end
|
252
|
+
|
253
|
+
def self.execute_transaction(project_id, zone_name)
|
254
|
+
gcloud "dns record-sets transaction execute --zone=#{zone_name}", project_id: project_id
|
255
|
+
end
|
256
|
+
|
257
|
+
def self.abort_transaction(args, project_id)
|
258
|
+
info "aborting dns record-set transaction", indent: 4
|
259
|
+
gcloud "dns record-sets transaction abort #{args}", project_id: project_id
|
260
|
+
# FIXME: remove transaction file..
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.record_is_valid(record)
|
264
|
+
if record["type"] == "CNAME" && !record["value"].end_with?(".")
|
265
|
+
info "CNAME value must end with '.'"
|
266
|
+
return false
|
267
|
+
end
|
268
|
+
|
269
|
+
true
|
270
|
+
end
|
271
|
+
|
272
|
+
def self.add_record_set(name, value, zone, type, ttl, project_id)
|
273
|
+
if Resource.resource?("dns record-sets", name, "--zone=#{zone}", filter: "name = #{name} AND type = #{type}", project_id: project_id, silent: true)
|
274
|
+
good "#{name} IN #{type} #{value} #{ttl}", indent: 4
|
275
|
+
return
|
276
|
+
end
|
277
|
+
|
278
|
+
add "#{name} IN #{type} #{value} #{ttl}", indent: 4
|
279
|
+
|
280
|
+
gcloud "dns record-sets transaction add --name=#{name} --zone=#{zone} --type=#{type} --ttl=#{ttl} #{value}", project_id: project_id
|
281
|
+
end
|
282
|
+
|
283
|
+
def self.lookup_ip(name, context)
|
284
|
+
args = context == "global" ? "--global" : "--regions #{context}"
|
285
|
+
ip = gcloud("compute addresses list #{name} #{args}", force: true)
|
286
|
+
return false if ip.empty?
|
287
|
+
ip[0]["address"]
|
288
|
+
end
|
289
|
+
|
290
|
+
def self.static_ip(project_id, zone_name, static_ip_config)
|
291
|
+
%w(name context).each do |key|
|
292
|
+
unless static_ip_config[key]
|
293
|
+
bad "missing key '#{key}' for record"
|
294
|
+
abort_transaction "--zone=#{zone_name}", project_id
|
295
|
+
fatal "failure due to invalid config"
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
name = static_ip_config["name"]
|
300
|
+
context = static_ip_config["context"]
|
301
|
+
|
302
|
+
ip = lookup_ip(name, context)
|
303
|
+
|
304
|
+
unless ip
|
305
|
+
unless cli_args[:dry_run]
|
306
|
+
bad "ip address not found for context/name: #{context}/#{name}"
|
307
|
+
abort_transaction "--zone=#{zone_name}", project_id
|
308
|
+
fatal "failure due to invalid config"
|
309
|
+
end
|
310
|
+
|
311
|
+
# on dry runs assume the ip address has not been created but config is valid
|
312
|
+
ip = "<#{context}/#{name}>"
|
313
|
+
end
|
314
|
+
|
315
|
+
ip
|
316
|
+
end
|
317
|
+
|
318
|
+
def self.describe_zone(project_id, zone_name)
|
319
|
+
gcloud "--format json dns managed-zones describe #{zone_name}", project_id: project_id, force: true
|
320
|
+
end
|
321
|
+
|
322
|
+
def self.zone_nameservers(project_id, zone_name)
|
323
|
+
remote_zone_definition = describe_zone(project_id, zone_name)
|
324
|
+
fatal "nameservers not found for zone: #{zone_name}" unless remote_zone_definition.key?("nameServers")
|
325
|
+
remote_zone_definition["nameServers"]
|
326
|
+
end
|
327
|
+
|
328
|
+
def self.dependencies
|
329
|
+
return unless project.key?("dns")
|
330
|
+
return unless project["dns"].key?("zones")
|
331
|
+
|
332
|
+
project["dns"]["zones"].each do |zone, zone_config|
|
333
|
+
project_id = zone_project_id(zone_config)
|
334
|
+
zone_name = zone.tr(".", "-")
|
335
|
+
|
336
|
+
# skip zone unless manage_nameservers is true
|
337
|
+
next unless zone_config.key?("manage_nameservers")
|
338
|
+
next unless zone_config["manage_nameservers"]
|
339
|
+
|
340
|
+
# parent zone data
|
341
|
+
parent_zone = zone.split(".")[1..-1].join(".")
|
342
|
+
parent_zone_name = parent_zone.tr(".", "-")
|
343
|
+
|
344
|
+
parent_zone_config = project["dns"]["zones"][parent_zone]
|
345
|
+
|
346
|
+
# get project_id for parent zone - if it isn't set then assume the zone exists in current project
|
347
|
+
parent_project_id = parent_zone_config.key?("project_id") ? parent_zone_config["project_id"] : project_id
|
348
|
+
|
349
|
+
info "ensuring nameservers for zone: #{zone}, project_id: #{parent_project_id}, parent_zone: #{parent_zone}"
|
350
|
+
|
351
|
+
next if cli_args[:dry_run]
|
352
|
+
|
353
|
+
# find nameservers for this zone
|
354
|
+
nameservers = zone_nameservers(project_id, zone_name)
|
355
|
+
|
356
|
+
# ensure parent zone exists
|
357
|
+
create_zone(parent_project_id, parent_zone, parent_zone_name)
|
358
|
+
|
359
|
+
# create nameservers in parent zone
|
360
|
+
start_transaction(parent_project_id, parent_zone_name)
|
361
|
+
add_record_set zone, nameservers.join(" "), parent_zone_name, "NS", 600, parent_project_id
|
362
|
+
execute_transaction(parent_project_id, parent_zone_name)
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
module Local
|
368
|
+
def self.list
|
369
|
+
GClouder::Resources::Global.instances(path: %w(dns zones))
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
module Remote
|
374
|
+
def self.list
|
375
|
+
zones.each_with_object({ "global" => [] }) do |zone, collection|
|
376
|
+
collection["global"] << { "name" => zone["name"], "records" => records(zone["name"]) }
|
377
|
+
end.delete_if { |_k, v| v.empty? }
|
378
|
+
end
|
379
|
+
|
380
|
+
def self.records(zone_name)
|
381
|
+
Resource.list("dns record-sets", "--zone #{zone_name}")
|
382
|
+
end
|
383
|
+
|
384
|
+
def self.zones
|
385
|
+
Resource.list("dns managed-zones").map { |zone| zone }
|
386
|
+
end
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
module GClouder
|
4
|
+
module Resources
|
5
|
+
module Logging
|
6
|
+
module Sinks
|
7
|
+
include GClouder::Config::CLIArgs
|
8
|
+
include GClouder::Config::Project
|
9
|
+
include GClouder::Logging
|
10
|
+
include GClouder::Resource::Cleaner
|
11
|
+
|
12
|
+
def self.header(stage = :ensure)
|
13
|
+
info "[#{stage}] logging / sinks", indent: 1, title: true
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.ensure
|
17
|
+
return if Local.list.empty?
|
18
|
+
header
|
19
|
+
|
20
|
+
Local.list.each do |region, sinks|
|
21
|
+
info region, indent: 2, heading: true
|
22
|
+
info
|
23
|
+
sinks.each do |sink|
|
24
|
+
Sink.ensure(sink)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.validate
|
30
|
+
return if Local.list.empty?
|
31
|
+
header :validate
|
32
|
+
Local.validate
|
33
|
+
end
|
34
|
+
|
35
|
+
module Local
|
36
|
+
include GClouder::Config::CLIArgs
|
37
|
+
include GClouder::Config::Project
|
38
|
+
include GClouder::Logging
|
39
|
+
|
40
|
+
def self.validate
|
41
|
+
return if list.empty?
|
42
|
+
|
43
|
+
failure = false
|
44
|
+
|
45
|
+
list.each do |region, sinks|
|
46
|
+
info region, indent: 2, heading: true
|
47
|
+
sinks.each do |sink|
|
48
|
+
info sink["name"], indent: 3, heading: true
|
49
|
+
if !sink["name"].is_a?(String)
|
50
|
+
bad "#{sink['name']} is incorrect type #{sink['name'].class}, should be: String", indent: 4
|
51
|
+
failure = true
|
52
|
+
end
|
53
|
+
|
54
|
+
if cli_args[:debug] || !cli_args[:output_validation]
|
55
|
+
good "name is a String", indent: 4
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
fatal "\nerror: validation failure" if failure
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.list
|
64
|
+
GClouder::Resources::Global.instances(path: %w(logging sinks))
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
module Remote
|
69
|
+
def self.list
|
70
|
+
{ "global" => instances.fetch("global", []).map { |sink| { "name" => sink["sink_id"] } } }.delete_if { |_k, v| v.empty? }
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.instances
|
74
|
+
Resources::Remote.instances(
|
75
|
+
path: %w(beta logging sinks)
|
76
|
+
)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
module Sink
|
81
|
+
include GClouder::GCloud
|
82
|
+
|
83
|
+
def self.args(sink)
|
84
|
+
"#{sink['destination']} " + hash_to_args(sink.delete_if { |k| k == "destination" })
|
85
|
+
end
|
86
|
+
|
87
|
+
def self.ensure(sink)
|
88
|
+
Resource.ensure :"beta logging sinks", sink["name"], args(sink)
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.purge(sink)
|
92
|
+
Resource.purge :"beta logging sinks", sink["name"]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|