gclouder 0.1.1
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 +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
|