fog-hetznercloud 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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE +201 -0
  4. data/README.md +139 -0
  5. data/Rakefile +24 -0
  6. data/bin/console +14 -0
  7. data/bin/setup +8 -0
  8. data/fog-hetznercloud.gemspec +33 -0
  9. data/lib/fog/hetznercloud.rb +13 -0
  10. data/lib/fog/hetznercloud/client.rb +55 -0
  11. data/lib/fog/hetznercloud/compute.rb +146 -0
  12. data/lib/fog/hetznercloud/errors.rb +37 -0
  13. data/lib/fog/hetznercloud/models/compute/action.rb +31 -0
  14. data/lib/fog/hetznercloud/models/compute/actions.rb +22 -0
  15. data/lib/fog/hetznercloud/models/compute/datacenter.rb +15 -0
  16. data/lib/fog/hetznercloud/models/compute/datacenters.rb +22 -0
  17. data/lib/fog/hetznercloud/models/compute/floating_ip.rb +154 -0
  18. data/lib/fog/hetznercloud/models/compute/floating_ips.rb +22 -0
  19. data/lib/fog/hetznercloud/models/compute/image.rb +78 -0
  20. data/lib/fog/hetznercloud/models/compute/images.rb +22 -0
  21. data/lib/fog/hetznercloud/models/compute/location.rb +18 -0
  22. data/lib/fog/hetznercloud/models/compute/locations.rb +22 -0
  23. data/lib/fog/hetznercloud/models/compute/server.rb +393 -0
  24. data/lib/fog/hetznercloud/models/compute/server_type.rb +17 -0
  25. data/lib/fog/hetznercloud/models/compute/server_types.rb +22 -0
  26. data/lib/fog/hetznercloud/models/compute/servers.rb +47 -0
  27. data/lib/fog/hetznercloud/models/compute/ssh_key.rb +65 -0
  28. data/lib/fog/hetznercloud/models/compute/ssh_keys.rb +22 -0
  29. data/lib/fog/hetznercloud/request_helper.rb +32 -0
  30. data/lib/fog/hetznercloud/requests/compute/create_floating_ip.rb +29 -0
  31. data/lib/fog/hetznercloud/requests/compute/create_server.rb +31 -0
  32. data/lib/fog/hetznercloud/requests/compute/create_ssh_key.rb +24 -0
  33. data/lib/fog/hetznercloud/requests/compute/delete_floating_ip.rb +17 -0
  34. data/lib/fog/hetznercloud/requests/compute/delete_image.rb +17 -0
  35. data/lib/fog/hetznercloud/requests/compute/delete_server.rb +17 -0
  36. data/lib/fog/hetznercloud/requests/compute/delete_ssh_key.rb +17 -0
  37. data/lib/fog/hetznercloud/requests/compute/execute_server_action.rb +17 -0
  38. data/lib/fog/hetznercloud/requests/compute/floating_ip_assign_to_server.rb +17 -0
  39. data/lib/fog/hetznercloud/requests/compute/floating_ip_unassign.rb +17 -0
  40. data/lib/fog/hetznercloud/requests/compute/floating_ip_update_dns_ptr.rb +17 -0
  41. data/lib/fog/hetznercloud/requests/compute/get_action.rb +17 -0
  42. data/lib/fog/hetznercloud/requests/compute/get_datacenter.rb +17 -0
  43. data/lib/fog/hetznercloud/requests/compute/get_floating_ip.rb +17 -0
  44. data/lib/fog/hetznercloud/requests/compute/get_image.rb +17 -0
  45. data/lib/fog/hetznercloud/requests/compute/get_location.rb +17 -0
  46. data/lib/fog/hetznercloud/requests/compute/get_server.rb +17 -0
  47. data/lib/fog/hetznercloud/requests/compute/get_server_type.rb +17 -0
  48. data/lib/fog/hetznercloud/requests/compute/get_ssh_key.rb +17 -0
  49. data/lib/fog/hetznercloud/requests/compute/list_actions.rb +17 -0
  50. data/lib/fog/hetznercloud/requests/compute/list_datacenters.rb +17 -0
  51. data/lib/fog/hetznercloud/requests/compute/list_floating_ips.rb +17 -0
  52. data/lib/fog/hetznercloud/requests/compute/list_images.rb +17 -0
  53. data/lib/fog/hetznercloud/requests/compute/list_locations.rb +17 -0
  54. data/lib/fog/hetznercloud/requests/compute/list_server_types.rb +17 -0
  55. data/lib/fog/hetznercloud/requests/compute/list_servers.rb +17 -0
  56. data/lib/fog/hetznercloud/requests/compute/list_ssh_keys.rb +17 -0
  57. data/lib/fog/hetznercloud/requests/compute/update_floating_ip.rb +17 -0
  58. data/lib/fog/hetznercloud/requests/compute/update_image.rb +17 -0
  59. data/lib/fog/hetznercloud/requests/compute/update_server.rb +77 -0
  60. data/lib/fog/hetznercloud/requests/compute/update_ssh_key.rb +17 -0
  61. data/lib/fog/hetznercloud/version.rb +5 -0
  62. metadata +244 -0
@@ -0,0 +1,22 @@
1
+ module Fog
2
+ module Hetznercloud
3
+ class Compute
4
+ class FloatingIps < Fog::Collection
5
+ model Fog::Hetznercloud::Compute::FloatingIp
6
+
7
+ def all(filters = {})
8
+ floating_ips = service.list_floating_ips(filters).body['floating_ips'] || []
9
+ load(floating_ips)
10
+ end
11
+
12
+ def get(identity)
13
+ if (floating_ip = service.get_floating_ip(identity).body['floating_ip'])
14
+ new(floating_ip)
15
+ end
16
+ rescue Fog::Hetznercloud::Compute::UnknownResourceError
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,78 @@
1
+ module Fog
2
+ module Hetznercloud
3
+ class Compute
4
+ class Image < Fog::Model
5
+ identity :id
6
+
7
+ attribute :type
8
+ attribute :status
9
+ attribute :name
10
+ attribute :description
11
+ attribute :image_size
12
+ attribute :disk_size
13
+ attribute :created
14
+ attribute :created_from
15
+ attribute :bound_to
16
+ attribute :os_flavor
17
+ attribute :os_version
18
+ attribute :rapid_deploy
19
+
20
+ def created=(value)
21
+ attributes[:created] = Time.iso8601(value)
22
+ end
23
+
24
+ def created_from=(value)
25
+ attributes[:created_from] = case value
26
+ when Hash
27
+ service.servers.new(value)
28
+ when Integer
29
+ service.servers.new(identity: value)
30
+ else
31
+ value
32
+ end
33
+ end
34
+
35
+ def bound_to=(value)
36
+ attributes[:bound_to] = case value
37
+ when Hash
38
+ service.servers.new(value)
39
+ when Integer
40
+ service.servers.new(identity: value)
41
+ else
42
+ value
43
+ end
44
+ end
45
+
46
+ def save
47
+ update
48
+ end
49
+
50
+ def destroy
51
+ requires :identity
52
+
53
+ service.delete_image(identity)
54
+ true
55
+ end
56
+
57
+ private
58
+
59
+ def update
60
+ requires :identity
61
+ requires_one :description, :type
62
+
63
+ body = attributes.dup
64
+
65
+ body[:description] = description
66
+ body[:type] = type
67
+
68
+ if (image = service.update_image(identity, body).body['image'])
69
+ merge_attributes(image)
70
+ true
71
+ else
72
+ false
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,22 @@
1
+ module Fog
2
+ module Hetznercloud
3
+ class Compute
4
+ class Images < Fog::Collection
5
+ model Fog::Hetznercloud::Compute::Image
6
+
7
+ def all(filters = {})
8
+ images = service.list_images(filters).body['images'] || []
9
+ load(images)
10
+ end
11
+
12
+ def get(identity)
13
+ if (image = service.get_image(identity).body['image'])
14
+ new(image)
15
+ end
16
+ rescue Fog::Hetznercloud::Compute::UnknownResourceError
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ # https://docs.hetzner.cloud/#resources-actions-get
2
+ module Fog
3
+ module Hetznercloud
4
+ class Compute
5
+ class Location < Fog::Model
6
+ identity :id
7
+
8
+ attribute :name
9
+ attribute :description
10
+ attribute :country
11
+ # attribute :extra_volumes
12
+ attribute :city
13
+ attribute :latitude
14
+ attribute :longitude
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Fog
2
+ module Hetznercloud
3
+ class Compute
4
+ class Locations < Fog::Collection
5
+ model Fog::Hetznercloud::Compute::Location
6
+
7
+ def all(filters = {})
8
+ locations = service.list_locations(filters).body['locations'] || []
9
+ load(locations)
10
+ end
11
+
12
+ def get(identity)
13
+ if (location = service.get_location(identity).body['location'])
14
+ new(location)
15
+ end
16
+ rescue Fog::Hetznercloud::Compute::UnknownResourceError
17
+ nil
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,393 @@
1
+ require 'fog/compute/models/server'
2
+
3
+ module Fog
4
+ module Hetznercloud
5
+ class Compute
6
+ class Server < Fog::Compute::Server
7
+ identity :id
8
+
9
+ # Required
10
+ attribute :name
11
+ attribute :image
12
+ attribute :server_type
13
+
14
+ attribute :status
15
+ attribute :created
16
+ attribute :user_data
17
+ attribute :ssh_keys
18
+ attribute :public_net
19
+ attribute :datacenter
20
+ attribute :location
21
+ attribute :iso
22
+ attribute :rescue_enabled
23
+ attribute :locked
24
+ attribute :backup_window
25
+ attribute :start_after_create
26
+ attribute :outgoing_traffic
27
+ attribute :incoming_traffic
28
+ attribute :included_traffic
29
+
30
+ def initialize(options)
31
+ @async = true
32
+ super(options)
33
+ end
34
+
35
+ def image=(value)
36
+ attributes[:image] = case value
37
+ when Hash
38
+ service.images.new(value)
39
+ when String
40
+ service.images.all(name: value).first
41
+ when Integer
42
+ service.images.get(value)
43
+ else
44
+ value
45
+ end
46
+ end
47
+
48
+ def ssh_keys=(value)
49
+ attributes[:ssh_keys] = []
50
+ # API does not return ssh_key
51
+ return true if value.nil?
52
+ value.each do |item|
53
+ thing = case item
54
+ when Hash
55
+ service.ssh_keys.new(item)
56
+ when String
57
+ service.ssh_keys.all(name: item).first
58
+ when Integer
59
+ service.ssh_keys.get(item)
60
+ else
61
+ value
62
+ end
63
+ attributes[:ssh_keys] << thing
64
+ end
65
+ end
66
+
67
+ def server_type=(value)
68
+ attributes[:server_type] = case value
69
+ when Hash
70
+ service.server_types.new(value)
71
+ when String
72
+ service.server_types.all(name: value).first
73
+ when Integer
74
+ service.server_types.get(value)
75
+ else
76
+ value
77
+ end
78
+ end
79
+
80
+ def location=(value)
81
+ attributes[:location] = case value
82
+ when Hash
83
+ service.locations.new(value)
84
+ when String
85
+ service.locations.all(name: value).first
86
+ when Integer
87
+ service.locations.get(value)
88
+ else
89
+ value
90
+ end
91
+ end
92
+
93
+ def datacenter=(value)
94
+ attributes[:datacenter] = case value
95
+ when Hash
96
+ service.datacenters.new(value)
97
+ attributes[:location] = service.locations.new(value['location'])
98
+ when String
99
+ service.datacenters.all(name: value).first
100
+ when Integer
101
+ service.datacenters.get(value)
102
+ else
103
+ value
104
+ end
105
+ end
106
+
107
+ def user_data=(value)
108
+ attributes[:user_data] = if value =~ /^\.?\/[^\/]+/
109
+ File.read(value)
110
+ else
111
+ value
112
+ end
113
+ end
114
+
115
+ def name=(value)
116
+ return true if name == value
117
+ @needsupdate = true
118
+ attributes[:name] = value
119
+ end
120
+
121
+ def public_dns_name
122
+ "static.#{public_net['ipv4']['ip']}.clients.your-server.de"
123
+ end
124
+
125
+ def reverse_dns_name
126
+ public_net['ipv4']['dns_ptr']
127
+ end
128
+
129
+ def ready?
130
+ status == 'running'
131
+ end
132
+
133
+ def migrating?
134
+ status == 'migrating'
135
+ end
136
+
137
+ def stopped?
138
+ status == 'off'
139
+ end
140
+
141
+ def save
142
+ if persisted?
143
+ update
144
+ else
145
+ create
146
+ end
147
+ end
148
+
149
+ def destroy
150
+ requires :identity
151
+
152
+ service.delete_server(identity)
153
+ true
154
+ end
155
+
156
+ def poweron(async: async_mode?)
157
+ requires :identity
158
+
159
+ execute_action('poweron', async)
160
+ end
161
+
162
+ def poweroff(async: async_mode?)
163
+ requires :identity
164
+
165
+ execute_action('poweroff', async)
166
+ end
167
+
168
+ def reboot(async: async_mode?)
169
+ requires :identity
170
+
171
+ execute_action('reboot', async)
172
+ end
173
+
174
+ def reset(async: async_mode?)
175
+ requires :identity
176
+
177
+ execute_action('reset', async)
178
+ end
179
+
180
+ def shutdown(async: async_mode?)
181
+ requires :identity
182
+
183
+ execute_action('shutdown', async)
184
+ end
185
+
186
+ def async=(value)
187
+ @async = !!value
188
+ end
189
+
190
+ def async?
191
+ @async
192
+ end
193
+
194
+ def async_mode?
195
+ async?
196
+ end
197
+
198
+ def sync=(value)
199
+ @async = !value
200
+ end
201
+
202
+ def sync?
203
+ !@async
204
+ end
205
+
206
+ def disable_rescue(async: async_mode?)
207
+ requires :identity
208
+
209
+ execute_action('disable_rescue', async)
210
+ end
211
+
212
+ def enable_rescue(type: 'linux64', ssh_keys: [], async: async_mode?)
213
+ requires :identity
214
+
215
+ # FIXME: Refactor with ssh_keys=(value) above to do DRY
216
+ sshkeys = []
217
+ ssh_keys.each do |item|
218
+ thing = case item
219
+ when Hash
220
+ service.ssh_keys.new(item)
221
+ when String
222
+ service.ssh_keys.all(name: item).first
223
+ when Integer
224
+ service.ssh_keys.get(item)
225
+ else
226
+ value
227
+ end
228
+ sshkeys << thing.identity
229
+ end
230
+
231
+ body = {
232
+ type: type,
233
+ ssh_keys: sshkeys
234
+ }
235
+
236
+ execute_action('enable_rescue', async, body)
237
+ end
238
+
239
+ # Returns action, imageObject
240
+ def create_backup(async: async_mode?, description: "Backup of #{attributes[:name]} created at #{Time.now}")
241
+ requires :identity
242
+
243
+ create_image(async: async, description: description, type: 'backup')
244
+ end
245
+
246
+ # Returns action, imageObject
247
+ def create_image(async: async_mode?, description: "image of #{attributes[:name]} created at #{Time.now}", type: 'snapshot')
248
+ requires :identity
249
+
250
+ body = {
251
+ description: description,
252
+ type: type
253
+ }
254
+
255
+ execute_action('create_image', async, body)
256
+ end
257
+
258
+ def rebuild_from_image(async: async_mode?, image:)
259
+ requires :identity
260
+
261
+ # FIXME: Lookup images by description. As API does not support
262
+ # this we need to filter our own. Probably needs pagination
263
+ # support.
264
+ image_id = case image
265
+ when Fog::Hetznercloud::Compute::Image
266
+ image.identity
267
+ when Integer
268
+ image
269
+ end
270
+
271
+ body = {
272
+ image: image_id
273
+ }
274
+
275
+ execute_action('rebuild', async, body)
276
+ end
277
+
278
+ def change_type(async: async_mode?, upgrade_disk: false, server_type:)
279
+ requires :identity
280
+
281
+ # FIXME: Should I shutdown and wait here as server needs to
282
+ # be stopped ?
283
+
284
+ body = {
285
+ upgrade_disk: upgrade_disk,
286
+ server_type: server_type
287
+ }
288
+
289
+ execute_action('change_type', async, body)
290
+ end
291
+
292
+ def enable_backup(async: async_mode?, backup_window: nil)
293
+ requires :identity
294
+
295
+ unless backup_window.nil?
296
+ body = {
297
+ backup_window: backup_window
298
+ }
299
+ end
300
+
301
+ execute_action('enable_backup', async, body)
302
+ end
303
+
304
+ def change_dns_ptr(dns_ptr, ip: public_ip_address, async: async_mode?)
305
+ requires :identity
306
+
307
+ body = {
308
+ ip: ip,
309
+ dns_ptr: dns_ptr
310
+ }
311
+
312
+ execute_action('change_dns_ptr', async, body)
313
+ end
314
+
315
+ def disable_backup(async: async_mode?)
316
+ requires :identity
317
+
318
+ execute_action('disable_backup', async)
319
+ end
320
+
321
+ # Returns action, password
322
+ def reset_root_password(async: async_mode?)
323
+ requires :identity
324
+ unless ready?
325
+ throw Fog::Hetznercloud::Error::StateError.new('ERROR: to reset the root password, the server must be running')
326
+ end
327
+
328
+ execute_action('reset_password', async)
329
+ end
330
+
331
+ def execute_action(action, async = true, body = {})
332
+ requires :identity
333
+
334
+ request_body = service.execute_server_action(identity, action, body).body
335
+ if (action = request_body['action'])
336
+ service.actions.new(action).tap do |a|
337
+ unless async
338
+ a.wait_for { a.success? }
339
+ reload
340
+ end
341
+ end
342
+ end
343
+ extra = service.images.new(request_body['image']) unless request_body['image'].nil?
344
+ extra = request_body['root_password'] unless request_body['root_password'].nil?
345
+ [action, extra]
346
+ end
347
+
348
+ def public_ip_address
349
+ public_net['ipv6']['ip'] if public_net.key?('ipv6')
350
+ public_net['ipv4']['ip'] if public_net.key?('ipv4')
351
+ end
352
+
353
+ private
354
+
355
+ def create
356
+ requires :name, :server_type, :image
357
+
358
+ options = {}
359
+ options[:ssh_keys] = ssh_keys.map(&:identity) unless ssh_keys.nil?
360
+ options[:user_data] = user_data unless user_data.nil?
361
+ options[:start_after_create] = start_after_create unless start_after_create.nil?
362
+ options[:location] = location.identity unless location.nil?
363
+ options[:datacenter] = datacenter.identity unless datacenter.nil?
364
+
365
+ if (server = service.create_server(name, image.identity, server_type.identity, options).body['server'])
366
+ merge_attributes(server)
367
+ true
368
+ else
369
+ false
370
+ end
371
+ end
372
+
373
+ def update
374
+ requires :identity, :name
375
+ return true unless @needsupdate
376
+
377
+ body = attributes.dup
378
+
379
+ body = {
380
+ name: name
381
+ }
382
+
383
+ if (server = service.update_server(identity, body).body['server'])
384
+ merge_attributes(server)
385
+ true
386
+ else
387
+ false
388
+ end
389
+ end
390
+ end
391
+ end
392
+ end
393
+ end