hcloud 0.1.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +0 -1
  3. data/.rubocop.yml +24 -9
  4. data/.rubocop_todo.yml +18 -121
  5. data/.travis.yml +3 -3
  6. data/CHANGELOG.md +13 -0
  7. data/Gemfile +3 -0
  8. data/Gemfile.lock +143 -0
  9. data/README.md +34 -1
  10. data/Rakefile +2 -0
  11. data/bin/console +1 -0
  12. data/hcloud.gemspec +5 -3
  13. data/lib/hcloud.rb +31 -5
  14. data/lib/hcloud/abstract_resource.rb +162 -55
  15. data/lib/hcloud/action.rb +8 -10
  16. data/lib/hcloud/action_resource.rb +3 -29
  17. data/lib/hcloud/client.rb +66 -28
  18. data/lib/hcloud/datacenter.rb +7 -7
  19. data/lib/hcloud/datacenter_resource.rb +6 -31
  20. data/lib/hcloud/entry_loader.rb +181 -20
  21. data/lib/hcloud/errors.rb +2 -0
  22. data/lib/hcloud/floating_ip.rb +18 -29
  23. data/lib/hcloud/floating_ip_resource.rb +15 -30
  24. data/lib/hcloud/image.rb +12 -32
  25. data/lib/hcloud/image_resource.rb +7 -38
  26. data/lib/hcloud/iso.rb +4 -1
  27. data/lib/hcloud/iso_resource.rb +7 -28
  28. data/lib/hcloud/location.rb +3 -9
  29. data/lib/hcloud/location_resource.rb +6 -27
  30. data/lib/hcloud/network.rb +33 -0
  31. data/lib/hcloud/network_resource.rb +25 -0
  32. data/lib/hcloud/pagination.rb +2 -9
  33. data/lib/hcloud/server.rb +37 -70
  34. data/lib/hcloud/server_resource.rb +16 -36
  35. data/lib/hcloud/server_type.rb +3 -10
  36. data/lib/hcloud/server_type_resource.rb +6 -28
  37. data/lib/hcloud/ssh_key.rb +6 -17
  38. data/lib/hcloud/ssh_key_resource.rb +13 -32
  39. data/lib/hcloud/typhoeus_ext.rb +112 -0
  40. data/lib/hcloud/version.rb +3 -1
  41. data/lib/hcloud/volume.rb +32 -0
  42. data/lib/hcloud/volume_resource.rb +29 -0
  43. metadata +31 -13
  44. data/lib/hcloud/multi_reply.rb +0 -21
@@ -1,13 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hcloud
2
4
  class Datacenter
3
- Attributes = {
4
- id: nil,
5
- name: nil,
6
- description: nil,
7
- location: Location,
8
- server_types: nil
9
- }.freeze
5
+ require 'hcloud/datacenter_resource'
10
6
 
11
7
  include EntryLoader
8
+
9
+ schema(
10
+ location: Location
11
+ )
12
12
  end
13
13
  end
@@ -1,44 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hcloud
2
4
  class DatacenterResource < AbstractResource
3
- include Enumerable
5
+ filter_attributes :name
4
6
 
5
- def all
6
- j = Oj.load(request('datacenters').run.body)
7
- dc = j['datacenters'].map { |x| Datacenter.new(x, self, client) }
8
- dc.reject { |x| x.id == j['recommendation'] }.unshift(
9
- dc.find { |x| x.id == j['recommendation'] }
10
- )
11
- end
7
+ bind_to Datacenter
12
8
 
13
9
  def recommended
14
10
  all.first
15
11
  end
16
12
 
17
- def find(id)
18
- Datacenter.new(
19
- Oj.load(request("datacenters/#{id}").run.body)['datacenter'],
20
- self,
21
- client
22
- )
23
- end
24
-
25
- def find_by(name:)
26
- x = Oj.load(request('datacenters', q: { name: name }).run.body)['datacenters']
27
- return nil if x.none?
28
- x.each do |s|
29
- return Datacenter.new(s, self, client)
30
- end
31
- end
32
-
33
13
  def [](arg)
34
14
  case arg
35
- when Integer
36
- begin
37
- find(arg)
38
- rescue Error::NotFound
39
- end
40
- when String
41
- find_by(name: arg)
15
+ when Integer then find_by(id: arg)
16
+ when String then find_by(name: arg)
42
17
  end
43
18
  end
44
19
  end
@@ -1,36 +1,197 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_support/concern'
4
+ require 'active_model'
5
+
2
6
  module Hcloud
3
- module EntryLoader
7
+ module EntryLoader # rubocop:disable Metrics/ModuleLength
4
8
  extend ActiveSupport::Concern
5
9
 
10
+ module Collection
11
+ attr_accessor :response
12
+ end
13
+
6
14
  included do |klass|
7
- klass.send(:attr_reader, :parent, :client)
8
- klass.const_get(:Attributes).each do |attribute, _value|
9
- klass.send(:attr_accessor, attribute)
10
- end
15
+ klass.send(:attr_writer, :client)
16
+ klass.include(ActiveModel::Dirty)
11
17
  end
12
18
 
13
- def initialize(resource, parent, client)
14
- @parent = parent
15
- @client = client
16
- self.class.const_get(:Attributes).each do |attribute, value|
17
- case value
18
- when nil
19
- send("#{attribute}=", resource[attribute.to_s])
20
- when :time
21
- unless resource[attribute.to_s].nil?
22
- send("#{attribute}=", Time.parse(resource[attribute.to_s]))
19
+ class_methods do # rubocop:disable Metrics/BlockLength
20
+ attr_accessor :resource_url
21
+
22
+ def _callbacks
23
+ @_callbacks ||= { before: [], after: [] }
24
+ end
25
+
26
+ %i[before after].each do |method|
27
+ define_method(method) { |&block| _callbacks[method] << block }
28
+ end
29
+
30
+ def schema(**kwargs)
31
+ @schema ||= {}.with_indifferent_access
32
+ return @schema if kwargs.empty?
33
+
34
+ @schema.merge!(kwargs)
35
+ end
36
+
37
+ def updatable(*args)
38
+ define_attribute_methods(*args)
39
+ args.each do |updateable_attribute|
40
+ define_method(updateable_attribute) { _attributes[updateable_attribute] }
41
+ define_method("#{updateable_attribute}=") do |value|
42
+ if _attributes[updateable_attribute] != value
43
+ public_send("#{updateable_attribute}_will_change!")
44
+ end
45
+ _update_attribute(updateable_attribute, value)
23
46
  end
24
- else
25
- if value.is_a?(Class) && value.include?(EntryLoader)
26
- send("#{attribute}=", value.new(resource[attribute.to_s], self, client))
47
+ end
48
+ end
49
+
50
+ def destructible
51
+ define_method(:destroy) { prepare_request(method: :delete) }
52
+ end
53
+
54
+ def protectable(*args)
55
+ define_method(:change_protection) do |**kwargs|
56
+ kwargs.keys.each do |key|
57
+ next if args.map(&:to_s).include? key.to_s
58
+
59
+ raise ArgumentError, "#{key} not an allowed protection mode (allowed: #{args})"
27
60
  end
61
+ prepare_request('actions/change_protection', j: kwargs)
62
+ end
63
+ end
64
+
65
+ def has_actions # rubocop:disable Naming/PredicateName
66
+ define_method(:actions) do
67
+ ActionResource.new(client: client, base_path: resource_url)
68
+ end
69
+ end
70
+
71
+ def resource_class
72
+ ancestors.reverse.find { |const| const.include?(Hcloud::EntryLoader) }
73
+ end
74
+
75
+ def from_response(response, autoload_action: nil)
76
+ attributes = response.resource_attributes
77
+ action = response.parsed_json[:action] if autoload_action
78
+ client = response.context.client
79
+ if attributes.is_a?(Array)
80
+ results = attributes.map { |item| new(item).tap { |entity| entity.response = response } }
81
+ results.tap { |ary| ary.extend(Collection) }.response = response
82
+ return results
83
+ end
84
+
85
+ return Action.new(client, action) if attributes.nil? && action
86
+ return new(attributes).tap { |entity| entity.response = response } if action.nil?
87
+
88
+ [
89
+ Action.new(client, action),
90
+ new(attributes).tap { |entity| entity.response = response }
91
+ ]
92
+ end
93
+ end
94
+
95
+ attr_accessor :response
96
+
97
+ def initialize(client = nil, **kwargs)
98
+ @client = client
99
+ _load(kwargs)
100
+ end
101
+
102
+ def inspect
103
+ "#<#{self.class.name}:0x#{__id__.to_s(16)} #{_attributes.inspect}>"
104
+ end
105
+
106
+ def client
107
+ @client || response&.context&.client
108
+ end
109
+
110
+ def resource_url
111
+ if block = self.class.resource_url
112
+ return instance_exec(&block)
113
+ end
114
+
115
+ [self.class.resource_class.name.demodulize.tableize, id].compact.join('/')
116
+ end
117
+
118
+ def resource_path
119
+ self.class.resource_class.name.demodulize.underscore
120
+ end
121
+
122
+ def prepare_request(url_suffix = nil, **kwargs, &block)
123
+ kwargs[:resource_path] ||= resource_path
124
+ kwargs[:resource_class] ||= self.class
125
+ kwargs[:autoload_action] = true unless kwargs.key?(:autoload_action)
126
+
127
+ client.prepare_request(
128
+ [resource_url, url_suffix].compact.join('/'),
129
+ **kwargs,
130
+ &block
131
+ )
132
+ end
133
+
134
+ def _attributes
135
+ @_attributes ||= {}.with_indifferent_access
136
+ end
137
+
138
+ def method_missing(method, *args, &block)
139
+ _attributes.key?(method) ? _attributes[method] : super
140
+ end
141
+
142
+ def respond_to_missing?(method, *args, &block)
143
+ _attributes.key?(method) || super
144
+ end
145
+
146
+ def update(**kwargs)
147
+ context = self
148
+ _run_callbacks(:before)
149
+ prepare_request(j: kwargs, method: :put) do |response|
150
+ response.resource_class.from_response(
151
+ response,
152
+ autoload_action: response.autoload_action
153
+ ).tap do |*_args|
154
+ _run_callbacks(:after)
155
+ context.send(:changes_applied)
28
156
  end
29
157
  end
30
158
  end
31
159
 
32
- def request(*args)
33
- client.request(*args)
160
+ def save
161
+ update(changes.map { |key, _value| [key.to_sym, _attributes[key]] }.to_h)
162
+ end
163
+
164
+ def rollback
165
+ restore_attributes
166
+ end
167
+
168
+ def _run_callbacks(order)
169
+ self.class._callbacks[order].each { |block| instance_exec(&block) }
170
+ end
171
+
172
+ def _update_attribute(key, value)
173
+ _attributes[key] = value
174
+ instance_variable_set("@#{key}", value)
175
+ end
176
+
177
+ def _load(resource)
178
+ @_attributes = {}.with_indifferent_access
179
+
180
+ resource.each do |key, value|
181
+ definition = self.class.schema[key]
182
+
183
+ if definition == :time
184
+ _update_attribute(key, value ? Time.parse(value) : nil)
185
+ next
186
+ end
187
+
188
+ if definition.is_a?(Class) && definition.include?(EntryLoader)
189
+ _update_attribute(key, value ? definition.new(client, value) : nil)
190
+ next
191
+ end
192
+
193
+ _update_attribute(key, value.is_a?(Hash) ? value.with_indifferent_access : value)
194
+ end
34
195
  end
35
196
  end
36
197
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hcloud
2
4
  class Error < StandardError
3
5
  class Unauthorized < Error; end
@@ -1,43 +1,32 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hcloud
2
4
  class FloatingIP
3
- Attributes = {
4
- id: nil,
5
- description: nil,
6
- ip: nil,
7
- type: nil,
8
- dns_ptr: nil,
9
- server: nil,
10
- home_location: Location,
11
- blocked: nil
12
- }.freeze
5
+ require 'hcloud/floating_ip_resource'
6
+
13
7
  include EntryLoader
14
8
 
15
- def update(description:)
16
- j = Oj.load(request("floating_ips/#{id.to_i}",
17
- j: { description: description },
18
- method: :put).run.body)
19
- FloatingIP.new(j['floating_ip'], self, client)
20
- end
9
+ schema(
10
+ home_location: Location,
11
+ created: :time
12
+ )
13
+
14
+ protectable :delete
15
+ updatable :description
16
+ destructible
17
+
18
+ has_actions
21
19
 
22
20
  def assign(server:)
23
- j = Oj.load(request("floating_ips/#{id.to_i}/actions/assign",
24
- j: { server: server }).run.body)
25
- Action.new(j['action'], self, client)
21
+ prepare_request('actions/assign', j: COLLECT_ARGS.call(__method__, binding))
26
22
  end
27
23
 
28
24
  def unassign
29
- j = Oj.load(request("floating_ips/#{id.to_i}/actions/unassign",
30
- method: :post).run.body)
31
- Action.new(j['action'], self, client)
32
- end
33
-
34
- def actions
35
- ActionResource.new(client: client, parent: self, base_path: "floating_ips/#{id.to_i}")
25
+ prepare_request('actions/unassign', method: :post)
36
26
  end
37
27
 
38
- def destroy
39
- request("floating_ips/#{id}", method: :delete).run.body
40
- true
28
+ def change_dns_ptr(ip:, dns_ptr:)
29
+ prepare_request('actions/change_dns_ptr', j: COLLECT_ARGS.call(__method__, binding))
41
30
  end
42
31
  end
43
32
  end
@@ -1,40 +1,25 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hcloud
2
4
  class FloatingIPResource < AbstractResource
3
- include Enumerable
5
+ filter_attributes :name
4
6
 
5
- def all
6
- mj('floating_ips') do |j|
7
- j.flat_map { |x| x['floating_ips'].map { |x| FloatingIP.new(x, self, client) } }
8
- end
9
- end
7
+ bind_to FloatingIP
10
8
 
11
- def create(type:, server: nil, home_location: nil, description: nil)
12
- query = {}
13
- method(:create).parameters.inject(query) do |r, x|
14
- (var = eval(x.last.to_s)).nil? ? r : r.merge!(x.last => var)
9
+ def [](arg)
10
+ case arg
11
+ when Integer then find_by(id: arg)
12
+ when String then find_by(name: arg)
15
13
  end
16
- j = Oj.load(request('floating_ips', j: query, code: 200).run.body)
17
- [
18
- j.key?('action') ? Action.new(j['action'], self, client) : nil,
19
- FloatingIP.new(j['floating_ip'], self, client)
20
- ]
21
14
  end
22
15
 
23
- def find(id)
24
- FloatingIP.new(
25
- Oj.load(request("floating_ips/#{id}").run.body)['floating_ip'],
26
- self,
27
- client
28
- )
29
- end
30
-
31
- def [](arg)
32
- case arg
33
- when Integer
34
- begin
35
- find(arg)
36
- rescue Error::NotFound
37
- end
16
+ def create(type:, server: nil, home_location: nil, description: nil)
17
+ prepare_request(
18
+ 'floating_ips', j: COLLECT_ARGS.call(__method__, binding),
19
+ expected_code: 201
20
+ ) do |response|
21
+ action = Action.new(client, response[:action]) if response[:action]
22
+ [action, FloatingIP.new(client, response[:floating_ip])]
38
23
  end
39
24
  end
40
25
  end
@@ -1,42 +1,22 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Hcloud
2
4
  class Image
3
- Attributes = {
4
- id: nil,
5
- type: nil,
6
- status: nil,
7
- name: nil,
8
- description: nil,
9
- image_size: nil,
10
- disk_size: nil,
11
- created: :time,
12
- created_from: nil,
13
- bound_to: nil,
14
- os_flavor: nil,
15
- os_version: nil,
16
- rapid_deploy: nil
17
- }.freeze
5
+ require 'hcloud/image_resource'
18
6
 
19
7
  include EntryLoader
20
8
 
21
- def to_snapshot
22
- update(type: 'snapshot')
23
- end
9
+ schema(
10
+ created: :time,
11
+ deprecated: :time
12
+ )
24
13
 
25
- def update(description: nil, type: nil)
26
- query = {}
27
- method(:update).parameters.inject(query) do |r, x|
28
- (var = eval(x.last.to_s)).nil? ? r : r.merge!(x.last => var)
29
- end
30
- Image.new(
31
- Oj.load(request("images/#{id.to_i}", j: query, method: :put).run.body)['image'],
32
- parent,
33
- client
34
- )
35
- end
14
+ protectable :delete
15
+ updatable :description, :type
16
+ destructible
36
17
 
37
- def destroy
38
- request("images/#{id.to_i}", method: :delete).run
39
- true
18
+ def to_snapshot
19
+ update(type: 'snapshot')
40
20
  end
41
21
  end
42
22
  end