hcloud 0.1.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +24 -9
- data/.rubocop_todo.yml +18 -121
- data/.travis.yml +3 -3
- data/CHANGELOG.md +13 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +143 -0
- data/README.md +34 -1
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/hcloud.gemspec +5 -3
- data/lib/hcloud.rb +31 -5
- data/lib/hcloud/abstract_resource.rb +162 -55
- data/lib/hcloud/action.rb +8 -10
- data/lib/hcloud/action_resource.rb +3 -29
- data/lib/hcloud/client.rb +66 -28
- data/lib/hcloud/datacenter.rb +7 -7
- data/lib/hcloud/datacenter_resource.rb +6 -31
- data/lib/hcloud/entry_loader.rb +181 -20
- data/lib/hcloud/errors.rb +2 -0
- data/lib/hcloud/floating_ip.rb +18 -29
- data/lib/hcloud/floating_ip_resource.rb +15 -30
- data/lib/hcloud/image.rb +12 -32
- data/lib/hcloud/image_resource.rb +7 -38
- data/lib/hcloud/iso.rb +4 -1
- data/lib/hcloud/iso_resource.rb +7 -28
- data/lib/hcloud/location.rb +3 -9
- data/lib/hcloud/location_resource.rb +6 -27
- data/lib/hcloud/network.rb +33 -0
- data/lib/hcloud/network_resource.rb +25 -0
- data/lib/hcloud/pagination.rb +2 -9
- data/lib/hcloud/server.rb +37 -70
- data/lib/hcloud/server_resource.rb +16 -36
- data/lib/hcloud/server_type.rb +3 -10
- data/lib/hcloud/server_type_resource.rb +6 -28
- data/lib/hcloud/ssh_key.rb +6 -17
- data/lib/hcloud/ssh_key_resource.rb +13 -32
- data/lib/hcloud/typhoeus_ext.rb +112 -0
- data/lib/hcloud/version.rb +3 -1
- data/lib/hcloud/volume.rb +32 -0
- data/lib/hcloud/volume_resource.rb +29 -0
- metadata +31 -13
- data/lib/hcloud/multi_reply.rb +0 -21
data/lib/hcloud/datacenter.rb
CHANGED
@@ -1,13 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hcloud
|
2
4
|
class Datacenter
|
3
|
-
|
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
|
-
|
5
|
+
filter_attributes :name
|
4
6
|
|
5
|
-
|
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
|
-
|
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
|
data/lib/hcloud/entry_loader.rb
CHANGED
@@ -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(:
|
8
|
-
klass.
|
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
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
33
|
-
|
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
|
data/lib/hcloud/errors.rb
CHANGED
data/lib/hcloud/floating_ip.rb
CHANGED
@@ -1,43 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hcloud
|
2
4
|
class FloatingIP
|
3
|
-
|
4
|
-
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
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
|
-
|
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
|
39
|
-
|
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
|
-
|
5
|
+
filter_attributes :name
|
4
6
|
|
5
|
-
|
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
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
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
|
data/lib/hcloud/image.rb
CHANGED
@@ -1,42 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Hcloud
|
2
4
|
class Image
|
3
|
-
|
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
|
-
|
22
|
-
|
23
|
-
|
9
|
+
schema(
|
10
|
+
created: :time,
|
11
|
+
deprecated: :time
|
12
|
+
)
|
24
13
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
38
|
-
|
39
|
-
true
|
18
|
+
def to_snapshot
|
19
|
+
update(type: 'snapshot')
|
40
20
|
end
|
41
21
|
end
|
42
22
|
end
|