ganeti_client 0.0.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.
data/README ADDED
@@ -0,0 +1,75 @@
1
+ == License
2
+
3
+ This Ruby Ganeti Client is release under AGPL licence (http://www.gnu.org/licenses/agpl-3.0.html)
4
+
5
+
6
+ == Todo
7
+
8
+ 1. Manually test the following methods + fix when bugs found
9
+ 1.1. redistribute_config
10
+ 1.2. instance_create
11
+ 1.3. instance_delete
12
+ 1.4. instance_reboot
13
+ 1.5. instance_shutdown
14
+ 1.6. instance_startup
15
+ 1.7. instance_reinstall
16
+ 1.8. instance_replace_disks
17
+ 1.9. instance_activate_disks
18
+ 1.10. instance_create_tags
19
+ 1.11. instance_delete_tags
20
+ 1.12. job_get
21
+ 1.13. job_delete
22
+ 1.14. node_evaluate
23
+ 1.15. node_migrate
24
+ 1.16. node_change_role
25
+ 1.17. node_get_storage
26
+ 1.18. node_modify_storage
27
+ 1.19. node_repair_storage
28
+ 1.20. node_get_tags
29
+ 1.21. node_create_tags
30
+ 1.22. node_delete_tags
31
+ 1.22. tags_create
32
+ 1.23. tags_delete
33
+ 2. Add some error handling
34
+ 3. Make some code improvements
35
+ 4. Add better comments
36
+ 5. Improve this README with better docs
37
+ 6. Write tests!
38
+
39
+
40
+ == About
41
+
42
+
43
+ == Installation
44
+
45
+ gem install ganeti_client
46
+
47
+
48
+ == Usage
49
+
50
+ # the last parameter is a boolean (true|false) to indicate if you want to display the response.
51
+ # this might be handy for debugging purposes
52
+ client = GanetiClient::Client.new("your-host-and-port", "username", "password", boolean)
53
+
54
+ # now you should be able to access the api resources by using the client instance.
55
+ # example:
56
+ info = client.get_info
57
+ => #<GanetiInfo:0x10151bb78>
58
+
59
+ # most methods return an object. When you use .to_json on an object, you get the json object returned
60
+ # then you can see all the attributes available
61
+ info.name
62
+ => "hostname"
63
+
64
+ == Contributing
65
+
66
+ 1. Fork the project
67
+ 2. Add your changes
68
+ 3. Write tests
69
+ 4. Send a pull request
70
+
71
+
72
+
73
+
74
+
75
+
@@ -0,0 +1,14 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'uri'
4
+ require 'base64'
5
+ require 'json'
6
+
7
+ require 'ganeti_client/client'
8
+ require 'ganeti_client/ganeti_object'
9
+
10
+
11
+ module GanetiClient
12
+
13
+
14
+ end
@@ -0,0 +1,562 @@
1
+ module GanetiClient
2
+ class Client
3
+
4
+ attr_accessor :conn, :headers, :version, :show_response
5
+
6
+ def initialize(host, username, password, show_response = false)
7
+ self.show_output = show_output
8
+ uri = URI.parse(host)
9
+
10
+ self.conn = Net::HTTP.new(uri.host, uri.port)
11
+ self.conn.use_ssl = (uri.scheme == "http")? false : true
12
+
13
+ self.headers = authenticate(username, password)
14
+
15
+ self.version = self.get_version
16
+ end
17
+
18
+
19
+ # Cluster information resource
20
+ # Returns cluster information
21
+ def info_get
22
+ url = get_url("info")
23
+ response_body = JSON.parse(send_request("GET", url))
24
+
25
+ create_class("GanetiInfo")
26
+
27
+ return GanetiInfo.new(response_body)
28
+ end
29
+
30
+ # Redistrite configuration to all nodes
31
+ # Redistribute configuration to all nodes. The result will be a job id.
32
+ def redistribute_config
33
+ url = get_url("redistribute-config")
34
+ response_body = send_request("PUT", url)
35
+
36
+ return response_body
37
+ end
38
+
39
+ # The instance resource
40
+ # Returns a list of all available instances
41
+ # If the optional bool bulk argument is provided and set to a true value (i.e ?bulk=1), the output contains detailed information about instances as a list.
42
+ def instances_get(bulk = 0)
43
+ url = get_url("instances", {"bulk" => bulk})
44
+ response_body = JSON.parse(send_request("GET", url))
45
+
46
+ create_class("GanetiInstance")
47
+
48
+ list = Array.new
49
+ response_body.each { |item| list << GanetiInstance.new(item) }
50
+
51
+ return list
52
+ end
53
+
54
+ # Creates an instance
55
+ # If the options bool dry-run argument is provided, the job will not be actually executed, only the pre-execution checks will be done.
56
+ # Query-ing the job result will return, in boty dry-run and normal case, the list of nodes selected for the instance
57
+ # Returns: a job ID that can be used later for polling
58
+ #
59
+ # Example of instance info:
60
+ #
61
+ def instance_create(info, dry_run = 0)
62
+ url = get_url("instances", {"dry-run" => dry_run})
63
+ body = JSON.generate(info)
64
+ response_body = send_request("POST", url, body)
65
+
66
+ return response_body
67
+ end
68
+
69
+ # Instance-specific resource
70
+ # Returns information about an instance, similar to the bulk output from the instance list
71
+ def instance_get(name)
72
+ url = get_url("instances/#{name}")
73
+ response_body = JSON.parse(send_request("GET", url))
74
+
75
+ create_class("GanetiInstance")
76
+
77
+ return GanetiInstance.new(response_body)
78
+ end
79
+
80
+ # Instance-specific resource
81
+ # Deletes an instance
82
+ # It supports the dry-run argument
83
+ def instance_delete(name, dry_run)
84
+ url = get_url("instances/#{name}", {"dry-run" => dry_run})
85
+ response_body = send_request("DELETE", url)
86
+
87
+ return response_body
88
+ end
89
+
90
+ # Requests detailed information about an instance.
91
+ # An optional parameter, static (bool), can be set to return only static information from the configuration without querying the instance's nodes
92
+ # The result will be a job id
93
+ def instance_get_info(name, static = 0)
94
+ url = get_url("instances/#{name}/info", {"static" => static})
95
+ response_body = send_request("GET", url)
96
+
97
+ return response_body
98
+ end
99
+
100
+ # Reboots URI for an instance
101
+ # Reboots the instance
102
+ # The URI takes optional type=soft|hard|full and ignore_secondaries=0|1 parameters
103
+ #
104
+ # type defines the reboot type.
105
+ # soft is just a normal reboot, without terminating the hypervisor.
106
+ # hard means full shutdown (including terminating the hypervisor process) and startup again
107
+ # full is like hard but also recreates the configuration from ground up as if you would have don a gnt-instance shutdown and gnt-instance start on it
108
+ #
109
+ # it supports the dry-run argument
110
+ def instance_reboot(name, type = "soft", ignore_secondaries = 0, dry_run = 0)
111
+ url = get_url("instances/#{name}/reboot", {"type" => type, "ignore_secondaries" => ignore_secondaries, "dry_run" => 0})
112
+ response_body = JSON.parse(send_request("POST", url))
113
+
114
+ create_class("GanetiInstanceReboot")
115
+
116
+ return GanetiIntanceReboot.new(response_body)
117
+ end
118
+
119
+
120
+ # Instance shutdown URI
121
+ # Shutdowns an instance
122
+ # It supports the dry-run argument
123
+ def instance_shutdown(name, dry_run = 0)
124
+ url = get_url("instances/#{name}/shutdown", {"dry-run" => dry_run})
125
+ response_body = JSON.parse(send_request("PUT", url))
126
+
127
+ create_class("GanetiInstanceShutdown")
128
+
129
+ return GanetiInstanceShutdown.new(response_body)
130
+ end
131
+
132
+ # Instance startup URI
133
+ # Startup an instance
134
+ # The URI takes an optional force=1|0 parameter to start the instance even if secondary disks are failing
135
+ # It supports the dry-run argument
136
+ def instance_startup(name, force = 0, dry_run=0)
137
+ url = get_url("instances/#{name}/startup", {"force" => force, "dry-run" => dry_run})
138
+ body = "" # force parameter
139
+ response_body = send_request("PUT", url, body)
140
+
141
+ create_class("GanetiInstanceStartup")
142
+
143
+ return GanetiInstanceStartup.new(response_body)
144
+ end
145
+
146
+ # Install the operating system again
147
+ # Takes the parameters os (OS template name) and nostartup (bool)
148
+ def instance_reinstall(name, os_name, nostartup)
149
+ url = get_url("instances/#{name}/reinstall", {"os" => os_name, "nostartup" => nostartup})
150
+ response_body = send_request("POST", url)
151
+
152
+ create_class("GanetiInstanceReinstall")
153
+
154
+ return GanetiInstanceReinstall.new(response_body)
155
+ end
156
+
157
+ # Replaces disks on an instance
158
+ # Takes the parameters mode (one of replace_on_primary, replace_on_secondary or replace_auto), disks (comma seperated list of disk indexes), remote_node and iallocator
159
+ # Either remote_node or iallocator needs to be defined when using mode=replace_new_secondary
160
+ # mode is a mandatory parameter. replace_auto tries to determine the broken disk(s) on its own and replacing it
161
+ def instance_replace_disks(name, mode = "replace_auto", iallocator = "", remote_node = "", disks = "")
162
+ url = get_url("instances/#{name}/replace-disks", {"mode" => mode, "iallocator" => iallocator, "remote_node" => remote_node, "disks" => disks})
163
+ response_body = send_request("POST", url)
164
+
165
+ return "?"
166
+ end
167
+
168
+ # Activate disks on an instance
169
+ # Takes the bool parameter ignore_size. When set ignore the recorded size (useful for forcing activation when recoreded size is wrong)
170
+ def intance_activate_disks(name, ignore_size = 0)
171
+ url = get_url("instances/#{name}/activate-disks", {"ignore_size" => ignore_size})
172
+ response_body = send_request("PUT", url)
173
+
174
+ return "?"
175
+ end
176
+
177
+ # Deactivate disks on an instance
178
+ # Takes no parameters
179
+ def instance_deactivate_disks(name)
180
+ url = get_url("instances/#{name}/deactivate-disks")
181
+ response_body = send_request("PUT", url)
182
+
183
+ return "?"
184
+ end
185
+
186
+ # Returns a list of tags
187
+ #
188
+ # Example:
189
+ # ["tag1", "tag2", "tag3"]
190
+ def instance_get_tags(name)
191
+ url = get_url("instances/#{name}/tags")
192
+ response_body = JSON.parse(send_request("GET", url))
193
+
194
+ return response_body
195
+ end
196
+
197
+ # Add a set of tags
198
+ # The request as a list of strings shoud be PUT tot this URI. The result will be a job id
199
+ # It supports the dry-run argument
200
+ def instance_create_tags(name, dry_run = 0)
201
+ url = get_url("instances/#{name}/tags", {"dry-run" => dry_run})
202
+ response_body = send_request("PUT", url)
203
+
204
+ return response_body
205
+ end
206
+
207
+ # Delete a tag
208
+ # In order to delete a set of tags, the DELETE request sould be addressed to URI LIKE:
209
+ # /tags?tag=[tag]&tag=[tag]
210
+ # It supports the dry-run argument
211
+ def instance_delete_tags(name, dry_run = 0)
212
+ url = get_url("instances/#{name}/tags", {"dry-run" => dry_run})
213
+ response_body = send_request("DELETE", url)
214
+
215
+ return "?"
216
+ end
217
+
218
+ # Returns a dictionary of jobs
219
+ # Returns: a dictionary with jobs id and uri
220
+ def jobs_get
221
+ url = get_url("jobs")
222
+ response_body = JSON.parse(send_request("GET", url))
223
+
224
+ create_class("GanetiJob")
225
+
226
+ list = Array.new
227
+ response_body.each { |item| list << GanetiJob.new(item) }
228
+
229
+ return list
230
+ end
231
+
232
+ # Individual job URI
233
+ # Return a job status
234
+ # Returns: a dictionary with job parameters
235
+ #
236
+ # The result includes:
237
+ # id: job ID as number
238
+ # status: current job status as a string
239
+ # ops: involved OpCodes as a list of dictionaries for each opcodes in the job
240
+ # opstatus: OpCodes status as a list
241
+ # opresult: OpCodes results as a list
242
+ #
243
+ # For a successful opcode, the opresult field corresponding to it will contain the raw result from its LogicalUnit. In case an opcode has failed, its element in the opresult list will be a list of two elements:
244
+ # first element the error type (the Ganeti internal error name)
245
+ # second element a list of either one or two elements:
246
+ # the first element is the textual error description
247
+ # the second element, if any, will hold an error classification
248
+ #
249
+ # The error classification is most useful for the OpPrereqError error type - these errors happen before the OpCode has started executing, so it’s possible to retry the
250
+ # OpCode without side effects. But whether it make sense to retry depends on the error classification:
251
+ #
252
+ # resolver_error
253
+ # Resolver errors. This usually means that a name doesn’t exist in DNS, so if it’s a case of slow DNS propagation the operation can be retried later.
254
+ #
255
+ # insufficient_resources
256
+ # Not enough resources (iallocator failure, disk space, memory, etc.). If the resources on the cluster increase, the operation might succeed.
257
+ #
258
+ # wrong_input
259
+ # Wrong arguments (at syntax level). The operation will not ever be accepted unless the arguments change.
260
+ #
261
+ # wrong_state
262
+ # Wrong entity state. For example, live migration has been requested for a down instance, or instance creation on an offline node. The operation can be retried once the resource has changed state.
263
+ #
264
+ # unknown_entity
265
+ # Entity not found. For example, information has been requested for an unknown instance.
266
+ #
267
+ # already_exists
268
+ # Entity already exists. For example, instance creation has been requested for an already-existing instance.
269
+ #
270
+ # resource_not_unique
271
+ # Resource not unique (e.g. MAC or IP duplication).
272
+ #
273
+ # internal_error
274
+ # Internal cluster error. For example, a node is unreachable but not set offline, or the ganeti node daemons are not working, etc. A gnt-cluster verify should be run.
275
+ #
276
+ # environment_error
277
+ # Environment error (e.g. node disk error). A gnt-cluster verify should be run.
278
+ #
279
+ # Note that in the above list, by entity we refer to a node or instance, while by a resource we refer to an instance’s disk, or NIC, etc.
280
+ def job_get(job_id)
281
+ url = get_url("jobs/#{job_id}")
282
+ response_body = JSON.parse(send_request("GET", url))
283
+
284
+ create_class("GanetiJob")
285
+
286
+ return GanetiJob.new(response_body)
287
+ end
288
+
289
+ # Cancel a not-yet-started job
290
+ def job_delete(job_id)
291
+ url = get_url("jobs/#{job_id}")
292
+ response = send_request("DELETE", url)
293
+
294
+ return "?"
295
+ end
296
+
297
+ # Nodes resource
298
+ # Returns a list of all nodes
299
+ # If the optional ‘bulk’ argument is provided and set to ‘true’ value (i.e ‘?bulk=1’).
300
+ #
301
+ # Returns detailed information about nodes as a list.
302
+ def nodes_get(bulk = 0)
303
+ url = get_url("nodes", {"bulk", bulk})
304
+ response_body = JSON.parse(send_request("GET", url))
305
+
306
+ create_class("GanetiNode")
307
+
308
+ list = Array.new
309
+ response_body.each { |item| list << GanetiNode.new(item) }
310
+
311
+ return list
312
+ end
313
+
314
+ # Returns information about a node
315
+ def node_get(name)
316
+ url = get_url("nodes/#{name}")
317
+ response_body = JSON.parse(send_request("GET", url))
318
+
319
+ create_class("GanetiNode")
320
+
321
+ return GanetiNode.new(response_body)
322
+ end
323
+
324
+ # Evacuates all secondary instances off a node.
325
+ # To evacuate a node, either one of the iallocator or remote_node parameters must be passed:
326
+ #
327
+ # Example:
328
+ # evacuate?iallocator=[iallocator]
329
+ # evacuate?remote_node=[nodeX.example.com]
330
+ def node_evauate(name, iallocator = "", remote_node = "")
331
+ url = get_url("nodes/#{name}/evacuate", {"iallocator" => iallocator, "remote_node" => remote_node})
332
+ response_body = send_request("POST", url)
333
+
334
+ return "?"
335
+ end
336
+
337
+ # Migrates all primary instances of a node
338
+ # No parameters are required, but the bool parameter live can be set to use live migration (if available)
339
+ #
340
+ # Example:
341
+ # migrate?live=[0|1]
342
+ def node_migrate(name, live = 0)
343
+ url = get_url("nodes/#{name}/migrate", {"live" => live})
344
+ response_body = send_request("POST", url)
345
+
346
+ return "?"
347
+ end
348
+
349
+ # Get the node role
350
+ # Returns the current node role
351
+ #
352
+ # Example:
353
+ # "master-candidate"
354
+ #
355
+ # The rol is always one of the following:
356
+ # drained
357
+ # master
358
+ # master-candidate
359
+ # offline
360
+ # regular
361
+ def node_get_role(name)
362
+ url = get_url("nodes/#{name}/role")
363
+ response_body = send_request("GET", url)
364
+
365
+ return response_body
366
+ end
367
+
368
+ # Change the node role
369
+ # the request is a string which shoud be PUT to this URI. The result will be a job id
370
+ # It supports the bool force argument
371
+ #
372
+ # The rol is always one of the following:
373
+ # drained
374
+ # master
375
+ # master-candidate
376
+ # offline
377
+ # regular
378
+ def node_change_role(name, role, force = 0)
379
+ url = get_url("nodes/#{name}/role", {"role" => role, "force" => foce})
380
+ response_body = send_request("PUT", url)
381
+
382
+ return response_body
383
+ end
384
+
385
+ # Manages storage units on the node
386
+ # Requests a list of storage units on a node. Requires the parameters storage_type (one of file, lvm-pv or lvm-vg) and output_fields. The result will be a job id, using which the result can be retrieved
387
+ def node_get_storage(name, storage_type = "", output_fields = "")
388
+ url = get_url("nodes/#{name}/storage", {"storage_type" => storage_type, "output_fields" => output_fields})
389
+ response_body = send_request("GET", url)
390
+
391
+ return response_body
392
+ end
393
+
394
+ # Modify storage units on the node
395
+ # Mofifies parameters of storage units on the node. Requires the parameters storage_type (one of file, lvm-pv or lvm-vg) and name (name of the storage unit).
396
+ # Parameters can be passed additionally. Currently only allocatable (bool) is supported.
397
+ #
398
+ # The result will be a job id.
399
+ def node_modify_storage(name, storage_type, allocatable = 0)
400
+ url = get_url("nodes/#{name}/storage/modify", {"storage_type" => storage_type, "allocatable" => allocatable})
401
+ response_body = send_request("PUT", url)
402
+
403
+ return response_body
404
+ end
405
+
406
+
407
+ # Repairs a storage unit on the node. Requires the parameters storage_type (currently only lvm-vg can be repaired) and name (name of the storage unit).
408
+ #
409
+ # The result will be a job id
410
+ def node_repair_storage(name, storage_type = "lvm-vg")
411
+ url = get_url("nodes/#{name}/storage/repair", {"storage_type" => storage_type})
412
+ reponse_body = send_request("PUT", url)
413
+
414
+ return response_body
415
+ end
416
+
417
+
418
+ # Manages per-node tags
419
+ # Returns a list of tags
420
+ #
421
+ # Example:
422
+ # ["tag1","tag2", "tag3"]
423
+ def node_get_tags(name)
424
+ url = get_url("nodes/#{name}/tags")
425
+ response_body = send_request("GET", url)
426
+
427
+ return response_body
428
+ end
429
+
430
+ # Add a set of tags
431
+ # The request as a list of strings should be PUT to this URI.
432
+ # It supports the dry-run argument
433
+ #
434
+ # The result will be a job id
435
+ def node_create_tags(name, tags, dry_run = 0)
436
+ url = get_url("nodes/#{name}/tags", {"tags" => tags, "dry-run" => dry_run})
437
+ response_body = send_request("PUT", url)
438
+
439
+ return response_body
440
+ end
441
+
442
+ # Deletes tags
443
+ # In order to delete a set of tags, the DELETE request should be addressed to URI like:
444
+ # /tags?tag=[tag]&tag=[tag]
445
+ #
446
+ # It supports the dry-run argument
447
+ def node_delete_tags(name, tags, dry_run = 0)
448
+ url = get_url("nodes/#{name}/tags", {"tags" => targs, "dry-run" => dry_run})
449
+ response_body = send_request("DELETE", url)
450
+
451
+ return response_body
452
+ end
453
+
454
+ # OS resource
455
+ # Returns a list of all OSes
456
+ #
457
+ # Can return error 500 in case of a problem. Since this is a costly operation for Ganeti 2.0, it is not recommented to execute it too often
458
+ #
459
+ # Example:
460
+ # ["debian-etch"]
461
+ def os_list_get
462
+ url = get_url("os")
463
+ response_body = JSON.parse(send_request("GET", url))
464
+
465
+ return response_body
466
+ end
467
+
468
+ # Manages cluster tags
469
+ # Returns the cluster tags
470
+ #
471
+ # Example:
472
+ # ["tag1", "tag2", "tag3"]
473
+ def tags_get
474
+ url = get_url("tags")
475
+ response_body = JSON.parse(send_request("GET", url))
476
+
477
+ return response_body
478
+ end
479
+
480
+ # Adds a set of tags
481
+ # The request as a list of strings should be PUT to this URI. The result will be a job id
482
+ #
483
+ # It supports the dry-run argument
484
+ def tags_create(tags, dry_run = 0)
485
+ url = get_url("tags", {"tags" => tags, "dry-run" => dry_run})
486
+ response_body = send_request("PUT", url)
487
+
488
+ return response_body
489
+ end
490
+
491
+ # Deletes tags
492
+ # In order to delete a set of tags, the DELETE request should be addressed to URI like:
493
+ # /tags?tag=[tag]&tag=[tag]
494
+ #
495
+ # It supports the dry-run argument
496
+ def tags_delete(tags, dry_run = 0)
497
+ url = get_url("tags", {"tags" => tags, "dry-run" => dry_run})
498
+ response_body = send_request("DELETE", url)
499
+
500
+ return response_body
501
+ end
502
+
503
+
504
+ # The version resource
505
+ # This resource should be used to determine the remote API version and to adapt client accordingly
506
+ # Returns the remote API version. Ganeti 1.2 returns 1 and Ganeti 2.0 returns 2
507
+ def version_get
508
+ url = get_url("version")
509
+ response_body = send_request("GET", url)
510
+
511
+ return response_body
512
+ end
513
+
514
+
515
+ private
516
+
517
+ def authenticate(username, password)
518
+ basic = Base64.b64encode("#{username}:#{password}")
519
+ headers = {'Authorization' => "Basic #{basic}"}
520
+
521
+ return headers
522
+ end
523
+
524
+ def get_url(path, params = nil)
525
+ param_string = ""
526
+
527
+ if params
528
+ params.each do |key, value|
529
+ if value.kind_of?(Array)
530
+ value.each do |svalue|
531
+ param_string += "#{key}=#{svalue}"
532
+ end
533
+ else
534
+ param_string += "#{key}=#{value}&"
535
+ end
536
+ end
537
+ end
538
+ return (self.version)? "/#{self.version}/#{path}?#{param_string}" : "/#{path}?#{param_string}"
539
+ end
540
+
541
+ def send_request(method, url, body = nil)
542
+ response = self.conn.send_request(method, url, body)
543
+
544
+ puts "Response #{response.code} #{response.message}: #{response.body}" if self.show_reponse
545
+ return response.body.strip
546
+ end
547
+
548
+ def create_class(class_name)
549
+ unless(class_exists?(class_name))
550
+ klass = Class.new GanetiClient::GanetiObject
551
+ Object.const_set(class_name, klass)
552
+ end
553
+ end
554
+
555
+ def class_exists?(class_name)
556
+ klass = Module.const_get(class_name)
557
+ return klass.is_a?(Class)
558
+ rescue NameError
559
+ return false
560
+ end
561
+ end
562
+ end
@@ -0,0 +1,16 @@
1
+ module GanetiClient
2
+ class GanetiObject
3
+
4
+ attr_accessor :json_object
5
+
6
+ def initialize(json = {})
7
+ self.json_object = json
8
+
9
+ json.each { |attr_name, attr_value| self.class.send(:define_method, attr_name.to_sym){ return attr_value } }
10
+ end
11
+
12
+ def to_json
13
+ return self.json_object
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ganeti_client
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - "Micha\xC3\xABl Rigart"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-08-15 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Google Ganeti RAPI client for Ruby
23
+ email: michael@netronix.be
24
+ executables: []
25
+
26
+ extensions: []
27
+
28
+ extra_rdoc_files:
29
+ - README
30
+ files:
31
+ - README
32
+ - lib/ganeti_client.rb
33
+ - lib/ganeti_client/client.rb
34
+ - lib/ganeti_client/ganeti_object.rb
35
+ has_rdoc: true
36
+ homepage: http://www.netronix.be
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --charset=UTF-8
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ hash: 3
50
+ segments:
51
+ - 0
52
+ version: "0"
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ requirements: []
63
+
64
+ rubyforge_project: nowarning
65
+ rubygems_version: 1.3.7
66
+ signing_key:
67
+ specification_version: 3
68
+ summary: Google Ganeti Client
69
+ test_files: []
70
+