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.
- checksums.yaml +7 -0
- data/Gemfile +4 -0
- data/LICENSE +201 -0
- data/README.md +139 -0
- data/Rakefile +24 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/fog-hetznercloud.gemspec +33 -0
- data/lib/fog/hetznercloud.rb +13 -0
- data/lib/fog/hetznercloud/client.rb +55 -0
- data/lib/fog/hetznercloud/compute.rb +146 -0
- data/lib/fog/hetznercloud/errors.rb +37 -0
- data/lib/fog/hetznercloud/models/compute/action.rb +31 -0
- data/lib/fog/hetznercloud/models/compute/actions.rb +22 -0
- data/lib/fog/hetznercloud/models/compute/datacenter.rb +15 -0
- data/lib/fog/hetznercloud/models/compute/datacenters.rb +22 -0
- data/lib/fog/hetznercloud/models/compute/floating_ip.rb +154 -0
- data/lib/fog/hetznercloud/models/compute/floating_ips.rb +22 -0
- data/lib/fog/hetznercloud/models/compute/image.rb +78 -0
- data/lib/fog/hetznercloud/models/compute/images.rb +22 -0
- data/lib/fog/hetznercloud/models/compute/location.rb +18 -0
- data/lib/fog/hetznercloud/models/compute/locations.rb +22 -0
- data/lib/fog/hetznercloud/models/compute/server.rb +393 -0
- data/lib/fog/hetznercloud/models/compute/server_type.rb +17 -0
- data/lib/fog/hetznercloud/models/compute/server_types.rb +22 -0
- data/lib/fog/hetznercloud/models/compute/servers.rb +47 -0
- data/lib/fog/hetznercloud/models/compute/ssh_key.rb +65 -0
- data/lib/fog/hetznercloud/models/compute/ssh_keys.rb +22 -0
- data/lib/fog/hetznercloud/request_helper.rb +32 -0
- data/lib/fog/hetznercloud/requests/compute/create_floating_ip.rb +29 -0
- data/lib/fog/hetznercloud/requests/compute/create_server.rb +31 -0
- data/lib/fog/hetznercloud/requests/compute/create_ssh_key.rb +24 -0
- data/lib/fog/hetznercloud/requests/compute/delete_floating_ip.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/delete_image.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/delete_server.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/delete_ssh_key.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/execute_server_action.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/floating_ip_assign_to_server.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/floating_ip_unassign.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/floating_ip_update_dns_ptr.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_action.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_datacenter.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_floating_ip.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_image.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_location.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_server.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_server_type.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/get_ssh_key.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_actions.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_datacenters.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_floating_ips.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_images.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_locations.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_server_types.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_servers.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/list_ssh_keys.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/update_floating_ip.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/update_image.rb +17 -0
- data/lib/fog/hetznercloud/requests/compute/update_server.rb +77 -0
- data/lib/fog/hetznercloud/requests/compute/update_ssh_key.rb +17 -0
- data/lib/fog/hetznercloud/version.rb +5 -0
- metadata +244 -0
@@ -0,0 +1,55 @@
|
|
1
|
+
module Fog
|
2
|
+
module Hetznercloud
|
3
|
+
class Client
|
4
|
+
def initialize(endpoint, token, connection_options)
|
5
|
+
@endpoint = endpoint
|
6
|
+
@token = token
|
7
|
+
@connection_options = connection_options
|
8
|
+
end
|
9
|
+
|
10
|
+
def request(params)
|
11
|
+
params[:headers] ||= {}
|
12
|
+
params[:headers]['Content-Type'] ||= 'application/json'
|
13
|
+
params[:headers]['Authorization'] ||= "Bearer #{@token}"
|
14
|
+
|
15
|
+
params[:body] = encode_body(params)
|
16
|
+
|
17
|
+
response = connection.request(params)
|
18
|
+
|
19
|
+
response.body = decode_body(response)
|
20
|
+
|
21
|
+
response
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def connection
|
27
|
+
@connection ||= Fog::Core::Connection.new(@endpoint, false, @connection_options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def encode_body(params)
|
31
|
+
body = params[:body]
|
32
|
+
content_type = params[:headers]['Content-Type']
|
33
|
+
|
34
|
+
if body.nil? || body.is_a?(String)
|
35
|
+
body
|
36
|
+
elsif content_type =~ %r{application/json.*}i
|
37
|
+
Fog::JSON.encode(body)
|
38
|
+
else
|
39
|
+
body.to_s
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def decode_body(response)
|
44
|
+
body = response.body
|
45
|
+
content_type = response.headers['Content-Type']
|
46
|
+
|
47
|
+
if !body.nil? && !body.empty? && content_type =~ %r{application/json.*}i
|
48
|
+
Fog::JSON.decode(body)
|
49
|
+
else
|
50
|
+
body
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
require 'fog/hetznercloud/client'
|
2
|
+
require 'fog/hetznercloud/errors'
|
3
|
+
require 'fog/hetznercloud/request_helper'
|
4
|
+
|
5
|
+
module Fog
|
6
|
+
module Hetznercloud
|
7
|
+
class Compute < Fog::Service
|
8
|
+
class UnauthorizedError < Error; end
|
9
|
+
class ForbiddenError < Error; end
|
10
|
+
class InvalidInputError < Error; end
|
11
|
+
class JsonErrorError < Error; end
|
12
|
+
class LockedError < Error; end
|
13
|
+
class NotFoundError < Error; end
|
14
|
+
class RateLimitExceededError < Error; end
|
15
|
+
class ResourceLimitExeceededError < Error; end
|
16
|
+
class ResourceUnavailableError < Error; end
|
17
|
+
class ServiceErrorError < Error; end
|
18
|
+
class UniquenessErrorError < Error; end
|
19
|
+
class UnknownResourceError < Error; end
|
20
|
+
class StateError < Error; end
|
21
|
+
|
22
|
+
requires :hetznercloud_token
|
23
|
+
recognizes :hetznercloud_datacenter
|
24
|
+
recognizes :hetznercloud_location
|
25
|
+
secrets :hetznercloud_token
|
26
|
+
|
27
|
+
model_path 'fog/hetznercloud/models/compute'
|
28
|
+
|
29
|
+
model :server
|
30
|
+
collection :servers
|
31
|
+
model :image
|
32
|
+
collection :images
|
33
|
+
model :server_type
|
34
|
+
collection :server_types
|
35
|
+
model :action
|
36
|
+
collection :actions
|
37
|
+
model :floating_ip
|
38
|
+
collection :floating_ips
|
39
|
+
model :location
|
40
|
+
collection :locations
|
41
|
+
model :datacenter
|
42
|
+
collection :datacenters
|
43
|
+
model :ssh_key
|
44
|
+
collection :ssh_keys
|
45
|
+
|
46
|
+
request_path 'fog/hetznercloud/requests/compute'
|
47
|
+
|
48
|
+
## Servers
|
49
|
+
request :create_server
|
50
|
+
request :list_servers
|
51
|
+
request :get_server
|
52
|
+
request :update_server
|
53
|
+
request :delete_server
|
54
|
+
request :execute_server_action
|
55
|
+
|
56
|
+
# Server Types
|
57
|
+
request :list_server_types
|
58
|
+
request :get_server_type
|
59
|
+
|
60
|
+
# Images
|
61
|
+
request :list_images
|
62
|
+
request :get_image
|
63
|
+
request :delete_image
|
64
|
+
request :update_image
|
65
|
+
|
66
|
+
# Actions
|
67
|
+
request :list_actions
|
68
|
+
request :get_action
|
69
|
+
|
70
|
+
# Locations
|
71
|
+
request :list_locations
|
72
|
+
request :get_location
|
73
|
+
|
74
|
+
# Datacenters
|
75
|
+
request :list_datacenters
|
76
|
+
request :get_datacenter
|
77
|
+
|
78
|
+
# Datacenters
|
79
|
+
request :list_ssh_keys
|
80
|
+
request :get_ssh_key
|
81
|
+
request :create_ssh_key
|
82
|
+
request :delete_ssh_key
|
83
|
+
request :update_ssh_key
|
84
|
+
|
85
|
+
# Floating IP'S
|
86
|
+
request :list_floating_ips
|
87
|
+
request :get_floating_ip
|
88
|
+
request :create_floating_ip
|
89
|
+
request :delete_floating_ip
|
90
|
+
request :update_floating_ip
|
91
|
+
request :floating_ip_assign_to_server
|
92
|
+
request :floating_ip_unassign
|
93
|
+
request :floating_ip_update_dns_ptr
|
94
|
+
|
95
|
+
class Real
|
96
|
+
include Fog::Hetznercloud::RequestHelper
|
97
|
+
|
98
|
+
# FIXME: Make @location and @datacenter used in server creation
|
99
|
+
# as default
|
100
|
+
def initialize(options)
|
101
|
+
@token = options[:hetznercloud_token]
|
102
|
+
@location = options[:hetznercloud_location] || 'fsn1'
|
103
|
+
@datacenter = options[:hetznercloud_datacenter] || 'fsn1-dc8'
|
104
|
+
@connection_options = options[:connection_options] || {}
|
105
|
+
end
|
106
|
+
|
107
|
+
def request(params)
|
108
|
+
client.request(params)
|
109
|
+
rescue Excon::Errors::HTTPStatusError => error
|
110
|
+
decoded = Fog::Hetznercloud::Errors.decode_error(error)
|
111
|
+
raise if decoded.nil?
|
112
|
+
|
113
|
+
code = decoded[:code]
|
114
|
+
message = decoded[:message]
|
115
|
+
|
116
|
+
raise case code
|
117
|
+
when 'unauthorized', 'forbidden', 'invalid_input', 'json_error', 'locked', 'not_found', 'rate_limit_exceeded', 'resource_limit_exceeded', 'resource_unavailable', 'service_error', 'uniqueness_error'
|
118
|
+
Fog::Hetznercloud::Compute.const_get("#{camelize(code)}Error").slurp(error, message)
|
119
|
+
else
|
120
|
+
Fog::Hetznercloud::Compute::Error.slurp(error, message)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def client
|
127
|
+
@client ||= Fog::Hetznercloud::Client.new(endpoint, @token, @connection_options)
|
128
|
+
end
|
129
|
+
|
130
|
+
def endpoint
|
131
|
+
'https://api.hetzner.cloud/v1'
|
132
|
+
end
|
133
|
+
|
134
|
+
def camelize(str)
|
135
|
+
str.split('_').collect(&:capitalize).join
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class Mock
|
140
|
+
def request(*_args)
|
141
|
+
Fog::Mock.not_implemented
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Fog
|
2
|
+
module Hetznercloud
|
3
|
+
module Errors
|
4
|
+
def self.decode_error(error)
|
5
|
+
body = begin
|
6
|
+
Fog::JSON.decode(error.response.body)
|
7
|
+
rescue Fog::JSON::DecodeError
|
8
|
+
nil
|
9
|
+
end
|
10
|
+
|
11
|
+
return if body.nil?
|
12
|
+
|
13
|
+
code = body['error']['code']
|
14
|
+
message = body['error']['message']
|
15
|
+
details = body['error']['details']
|
16
|
+
|
17
|
+
return if code.nil? || message.nil?
|
18
|
+
|
19
|
+
unless details.nil?
|
20
|
+
message << "\n"
|
21
|
+
message << format_details(details)
|
22
|
+
end
|
23
|
+
|
24
|
+
{ code: code, message: message }
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.format_details(details)
|
28
|
+
details.map { |field, msgs| format_field(field, msgs) }.join("\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.format_field(field, msgs)
|
32
|
+
msgs = msgs.map { |msg| "\t\t- #{msg}" }
|
33
|
+
"\t#{field}:\n#{msgs.join("\n")}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# https://docs.hetzner.cloud/#resources-actions-get
|
2
|
+
module Fog
|
3
|
+
module Hetznercloud
|
4
|
+
class Compute
|
5
|
+
class Action < Fog::Model
|
6
|
+
identity :id
|
7
|
+
|
8
|
+
attribute :command
|
9
|
+
attribute :status
|
10
|
+
attribute :progress
|
11
|
+
# attribute :extra_volumes
|
12
|
+
attribute :started
|
13
|
+
attribute :finished
|
14
|
+
attribute :resources
|
15
|
+
attribute :error
|
16
|
+
|
17
|
+
def running?
|
18
|
+
status == 'running'
|
19
|
+
end
|
20
|
+
|
21
|
+
def error?
|
22
|
+
status == 'error'
|
23
|
+
end
|
24
|
+
|
25
|
+
def success?
|
26
|
+
status == 'success'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Fog
|
2
|
+
module Hetznercloud
|
3
|
+
class Compute
|
4
|
+
class Actions < Fog::Collection
|
5
|
+
model Fog::Hetznercloud::Compute::Action
|
6
|
+
|
7
|
+
def all(filters = {})
|
8
|
+
actions = service.list_actions(filters).body['actions'] || []
|
9
|
+
load(actions)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(identity)
|
13
|
+
if (action = service.get_action(identity).body['action'])
|
14
|
+
new(action)
|
15
|
+
end
|
16
|
+
rescue Fog::Hetznercloud::Compute::UnknownResourceError
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# https://docs.hetzner.cloud/#resources-actions-get
|
2
|
+
module Fog
|
3
|
+
module Hetznercloud
|
4
|
+
class Compute
|
5
|
+
class Datacenter < Fog::Model
|
6
|
+
identity :id
|
7
|
+
|
8
|
+
attribute :name
|
9
|
+
attribute :description
|
10
|
+
attribute :location
|
11
|
+
attribute :server_types
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Fog
|
2
|
+
module Hetznercloud
|
3
|
+
class Compute
|
4
|
+
class Datacenters < Fog::Collection
|
5
|
+
model Fog::Hetznercloud::Compute::Datacenter
|
6
|
+
|
7
|
+
def all(filters = {})
|
8
|
+
datacenters = service.list_datacenters(filters).body['datacenters'] || []
|
9
|
+
load(datacenters)
|
10
|
+
end
|
11
|
+
|
12
|
+
def get(identity)
|
13
|
+
if (datacenter = service.get_datacenter(identity).body['datacenter'])
|
14
|
+
new(datacenter)
|
15
|
+
end
|
16
|
+
rescue Fog::Hetznercloud::Compute::UnknownResourceError
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# https://docs.hetzner.cloud/#resources-actions-get
|
2
|
+
module Fog
|
3
|
+
module Hetznercloud
|
4
|
+
class Compute
|
5
|
+
class FloatingIp < Fog::Model
|
6
|
+
identity :id
|
7
|
+
|
8
|
+
attribute :description # String
|
9
|
+
attribute :ip # String
|
10
|
+
attribute :type # ipv4|ipv6
|
11
|
+
attribute :server # id of server
|
12
|
+
attribute :dns_ptr # array of string
|
13
|
+
attribute :home_location # location object
|
14
|
+
attribute :blocked # boolean
|
15
|
+
|
16
|
+
def type=(value)
|
17
|
+
valid = %w[ipv4 ipv6]
|
18
|
+
if !valid.include? value
|
19
|
+
raise Fog::Hetznercloud::Compute::InvalidInputError, "ERROR: floating_ip type must be one of #{valid}"
|
20
|
+
else
|
21
|
+
attributes[:type] = value
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def description=(value)
|
26
|
+
@lastdescription = attributes[:description]
|
27
|
+
attributes[:description] = value
|
28
|
+
if @lastdescription && @lastdescription != attributes[:description]
|
29
|
+
@needsupdate = true
|
30
|
+
else
|
31
|
+
@needsupdate = false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def home_location=(value)
|
36
|
+
attributes[:home_location] = case value
|
37
|
+
when Hash
|
38
|
+
service.locations.new(value)
|
39
|
+
when String
|
40
|
+
service.locations.new(identity: value)
|
41
|
+
else
|
42
|
+
value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def server=(value)
|
47
|
+
attributes[:server] = case value
|
48
|
+
when Hash
|
49
|
+
service.servers.new(value)
|
50
|
+
when Integer
|
51
|
+
service.servers.get(value)
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def destroy
|
58
|
+
requires :identity
|
59
|
+
|
60
|
+
service.delete_floating_ip(identity)
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def save
|
65
|
+
if persisted?
|
66
|
+
reassign && unassign && update
|
67
|
+
else
|
68
|
+
create
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def assign(serverid)
|
73
|
+
requires :identity
|
74
|
+
if serverid.nil?
|
75
|
+
body = {}
|
76
|
+
|
77
|
+
if (floating_ip = service.floating_ip_unassign(identity, body).body['floating_ip'])
|
78
|
+
merge_attributes(floating_ip)
|
79
|
+
true
|
80
|
+
else
|
81
|
+
false
|
82
|
+
end
|
83
|
+
else
|
84
|
+
body = {
|
85
|
+
server: serverid
|
86
|
+
}
|
87
|
+
|
88
|
+
if (floating_ip = service.floating_ip_assign_to_server(identity, body).body['floating_ip'])
|
89
|
+
merge_attributes(floating_ip)
|
90
|
+
true
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def update_dns_ptr(newname)
|
98
|
+
requires :identity
|
99
|
+
|
100
|
+
body = {
|
101
|
+
ip: ip,
|
102
|
+
dns_ptr: newname
|
103
|
+
}
|
104
|
+
|
105
|
+
if (floating_ip = service.floating_ip_update_dns_ptr(identity, body).body['floating_ip'])
|
106
|
+
merge_attributes(floating_ip)
|
107
|
+
true
|
108
|
+
else
|
109
|
+
false
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def unassign
|
114
|
+
assign(nil) unless server.nil?
|
115
|
+
end
|
116
|
+
|
117
|
+
private
|
118
|
+
|
119
|
+
def create
|
120
|
+
requires :type
|
121
|
+
requires_one :home_location, :server
|
122
|
+
|
123
|
+
options = {}
|
124
|
+
options[:description] = description unless description.nil?
|
125
|
+
options[:home_location] = home_location.identity unless home_location.nil?
|
126
|
+
options[:server] = server.identity unless server.nil?
|
127
|
+
|
128
|
+
if (floating_ip = service.create_floating_ip(type, options).body['floating_ip'])
|
129
|
+
merge_attributes(floating_ip)
|
130
|
+
true
|
131
|
+
else
|
132
|
+
false
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def update
|
137
|
+
return true unless @needsupdate
|
138
|
+
requires :identity, :description
|
139
|
+
|
140
|
+
body = attributes.dup
|
141
|
+
|
142
|
+
body[:description] = description
|
143
|
+
|
144
|
+
if (floating_ip = service.update_floating_ip(identity, body).body['floating_ip'])
|
145
|
+
merge_attributes(floating_ip)
|
146
|
+
true
|
147
|
+
else
|
148
|
+
false
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|