fog-hetznercloud 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|