fog-hetznercloud 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 624b520019fef559e39234bfaeaefe1c84fdccb3
4
- data.tar.gz: a3cc609598f1ecb0353256e2a94e9fd90638a5d8
3
+ metadata.gz: f2513d94d86a7673b99f59b766e1a0eda8dc0106
4
+ data.tar.gz: 818b34b6423ee7a51c25dcfc3b163883cdb52814
5
5
  SHA512:
6
- metadata.gz: badcbf8b5728b9d9af029ce70b1dd8cb8d9329200305fc8f8b56f33d483939e73eb667a4a91a21e48d20f1dd8990002abc93ebd39fded9cc19cea7f84186b936
7
- data.tar.gz: 6b09eac7483499e5e6a8962ac45b696f42fdfd217c984df0cb4001b24a79c01c719889b0887ef9aa0a2b7c41ff3defdb0d75bc9c74f54d57e13ea4199d87839c
6
+ metadata.gz: cef2459d83661bc34425320e27cc348224bbacd0257d3db921ba5eb9193856522bfc2742e051660c59e803140edf6b682f23289afdeb01503bf264fd29c61eca
7
+ data.tar.gz: 10d33c8176c6b505f91d299d1a27c17f8332c6f85098e972b7db22fd382930e37755e03b50ef1dce62c4921b39deb3af4445dcc36fd2f792d37d132a9ce5139f
data/README.md CHANGED
@@ -1,11 +1,12 @@
1
1
 
2
+
2
3
  # Rubygem fog-hetznercloud
3
4
 
4
5
  Fog provider gem to support [Hetzner Cloud](https://cloud.hetzner.com/).
5
6
 
6
7
  ## Features and development
7
8
 
8
- This gem is currently a MVP prototype that is missing some features:
9
+ This gem is currently a MVP prototype that is **missing** some features like:
9
10
 
10
11
  API:
11
12
  * [Pagination Support](https://docs.hetzner.cloud/#header-pagination-1)
@@ -53,7 +54,9 @@ default:
53
54
 
54
55
  ## Example
55
56
 
56
- ### Gemfile
57
+ **See [examples/integrationtest.rb](examples/integrationtest.rb) for a reasonable example**
58
+
59
+ ### Ge mfile
57
60
 
58
61
  ```ruby
59
62
  source "https://rubygems.org"
data/Rakefile CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'bundler/gem_tasks'
2
2
  require 'rake/testtask'
3
3
  require 'rubocop/rake_task'
4
+ require 'bump/tasks'
4
5
 
5
6
  Rake::TestTask.new('test:units') do |t|
6
7
  t.libs << 'test'
data/examples/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ #gem 'fog-hetznercloud', '>= 0.0.1'
6
+ gem 'fog-hetznercloud', :path => '../'
7
+
8
+ # gem "rails"
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env ruby2.0
2
+ #
3
+ # This is a simple integraiton test performing some usefull server actions.
4
+ #
5
+ # This is also intended as some kind of documentation on how to use the library
6
+ #
7
+ #
8
+ require 'fog/hetznercloud'
9
+ require 'pp'
10
+
11
+ # Connecto to Hetznercloud
12
+ connection = Fog::Compute[:hetznercloud]
13
+
14
+ # Some variables
15
+ ssh_public_keyfile = "./keys/testkey.pub" # needs to be generated first with 'ssh-keygen -t rsa testkey'
16
+ ssh_private_keyfile = "./keys/testkey" # needs to be generated first with 'ssh-keygen -t rsa testkey'
17
+ ssh_keyname = "testkey" # Name of the ssh key
18
+ servername = "testserver" # Name of the server
19
+
20
+ # resources created
21
+ image_snapshot = nil
22
+ image_backup = nil
23
+ server = nil
24
+ floating_ip = nil
25
+ ssh_key = nil
26
+
27
+ raise "Error: Fild #{ssh_private_keyfile} missing (create with 'ssh-keygen -t rsa -N \"\" -f #{ssh_private_keyfile}')" unless File.file?(ssh_private_keyfile)
28
+
29
+ begin
30
+
31
+ # Lookups, see api doc https://docs.hetzner.cloud for filters
32
+ connection.datacenters.each { |x| puts "Datacenter #{x.name} located in #{x.location.name}" }
33
+ connection.locations.each { |x| puts "Location #{x.name} located in #{x.city}" }
34
+ connection.locations.all(:name => 'fsn1').each { |x| puts "FSN Found" }
35
+ connection.locations.all(:name => 'nbg1').each { |x| puts "NBG Found" }
36
+ connection.server_types.each { |x| puts "Server Type #{x.name} (#{x.cores}/#{x.memory}/#{x.disk})" }
37
+ connection.server_types.all(:name => 'cx11').each { |x| puts "Server cx11 found" }
38
+
39
+ ## create or select ssh key ...
40
+ ssh_key = connection.ssh_keys.all(:name => ssh_keyname).first
41
+ if !ssh_key
42
+ puts "Creating SSH Key ..."
43
+ ssh_key = connection.ssh_keys.create(:name => ssh_keyname, :public_key => ssh_public_keyfile)
44
+ else
45
+ puts "Using existing SSH Key ..."
46
+ end
47
+
48
+ puts "Creating Floating IP"
49
+ floating_ip = connection.floating_ips.create(:type => 'ipv4', :home_location => 'fsn1' )
50
+ puts "Using existing VIP #{floating_ip.ip} #{floating_ip.server.nil? ? 'unbound' : 'assigned to' + floating_ip.server.name}"
51
+
52
+ # lookup existing server by name or create new server, works with most resources
53
+ server = connection.servers.all(:name => servername).first
54
+ if !server
55
+ puts "Bootstrapping Server (waiting until ssh is ready)..."
56
+ server = connection.servers.bootstrap(
57
+ :name => servername,
58
+ :image => 'centos-7',
59
+ :server_type => 'cx11',
60
+ :location => 'nbg1',
61
+ :user_data => "./userdata.txt",
62
+ :private_key_path => ssh_private_keyfile,
63
+ :ssh_keys => [ ssh_key.identity ],
64
+ :bootstap_timeout => 120,
65
+ )
66
+ else
67
+ puts "Using existing Server ... "
68
+ end
69
+ puts "Server public_ip_address:#{server.public_ip_address} (#{server.public_dns_name}/#{server.reverse_dns_name})"
70
+
71
+ puts "Assign VIP #{floating_ip.ip} to #{server.name}"
72
+ floating_ip.assign(server)
73
+
74
+ # now wait for the server to boot
75
+ puts "Waiting for Server SSH ..."
76
+ server.wait_for { server.sshable? }
77
+
78
+ puts "Adding VIP to server"
79
+ server.ssh("/sbin/ip addr add dev eth0 #{floating_ip.ip}")
80
+
81
+ puts "Changing Root Password"
82
+ action,password = server.reset_root_password
83
+ puts "New Root password #{password}"
84
+
85
+ puts "Unassigning VIP"
86
+ floating_ip.unassign()
87
+
88
+ puts "SSH in server ..."
89
+ puts server.ssh('cat /tmp/test').first.stdout # => "test1\r\n"
90
+
91
+ puts "Setting API to syncronous mode (wait for action to complete)"
92
+ server.sync=true
93
+
94
+ puts "Create Image (Sync) ..."
95
+ action, image_snapshot = server.create_image()
96
+
97
+ puts "Enable Backup ..."
98
+ server.enable_backup
99
+
100
+ puts "Create Backup (ASync) ..."
101
+ action, image_backup = server.create_backup()
102
+
103
+ puts "Disable Backup ..."
104
+ server.enable_backup
105
+
106
+ # Boot Server in rescue system with SSH Keys
107
+ puts "Booting into rescue mode"
108
+ server.enable_rescue(:ssh_keys => [ ssh_keyname ])
109
+ server.reboot()
110
+ server.wait_for { server.sshable? }
111
+
112
+ puts "SSH in Rescue System ..."
113
+ puts server.ssh('hostname').first.stdout # => "test1\r\n"
114
+
115
+ # Reboot again
116
+ puts "Booting into normal mode"
117
+ server.disable_rescue()
118
+ server.reset()
119
+ server.wait_for { server.sshable? }
120
+
121
+ # Change Server Type
122
+ puts "Changing Server Type to cx21"
123
+ server.shutdown()
124
+ # FIXME: Handle in GEM, sometimes API error ?
125
+ server.wait_for { server.stopped? }
126
+ server.change_type(:upgrade_disk => false, :server_type => 'cx21')
127
+ server.poweron()
128
+ server.wait_for { server.sshable? }
129
+
130
+ puts server.ssh('hostname').first.stdout # => "test1\r\n"
131
+
132
+ # Change PTR
133
+ puts "Changing PTR"
134
+ server.change_dns_ptr('www.elconas.de')
135
+ server.reload
136
+
137
+ puts "PowerOff Server"
138
+ server.poweroff
139
+
140
+ puts "Setting API to asynchronous mode"
141
+ server.async=true
142
+
143
+ puts "Destroy Server ..."
144
+ server.destroy
145
+ rescue Exception => e
146
+ server.destroy unless server.nil?
147
+ image_snapshot.destroy unless image_snapshot.nil?
148
+ image_backup.destroy unless image_backup.nil?
149
+ floating_ip.destroy unless floating_ip.nil?
150
+ ssh_key.destroy unless ssh_key.nil?
151
+
152
+ ## List Accounts
153
+ connection.servers.each { |x|
154
+ puts "Server #{x.id}: #{x.name} #{x.public_ip_address}"
155
+ }
156
+ connection.floating_ips.each { |x|
157
+ puts "Floating IP #{x.id}: #{x.ip}"
158
+ }
159
+ connection.images.all(:type => 'snapshot').each { |x|
160
+ puts "Snapshot Image #{x.id}: #{x.description}"
161
+ }
162
+ connection.images.all(:type => 'backup').each { |x|
163
+ puts "Backup Image #{x.id}: #{x.description}"
164
+ }
165
+ raise e
166
+ end
File without changes
@@ -0,0 +1,3 @@
1
+ #cloud-config
2
+ runcmd:
3
+ - hostname > /tmp/test
@@ -14,7 +14,6 @@ Gem::Specification.new do |spec|
14
14
  spec.license = 'APACHE-2.0'
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0").reject { |f|
17
- puts "Checking file #{f}"
18
17
  f.match(%r{^(test|spec|features)/}) || f.match(%r{^\.})
19
18
  }
20
19
  spec.require_paths = ['lib']
@@ -22,12 +21,13 @@ Gem::Specification.new do |spec|
22
21
  spec.add_development_dependency 'bundler'
23
22
  spec.add_development_dependency 'coveralls'
24
23
  spec.add_development_dependency 'minitest'
25
- spec.add_development_dependency 'net-ssh'
26
24
  spec.add_development_dependency 'pry'
27
25
  spec.add_development_dependency 'rake'
28
26
  spec.add_development_dependency 'rubocop'
29
27
  spec.add_development_dependency 'simplecov'
28
+ spec.add_development_dependency 'bump'
30
29
 
31
30
  spec.add_dependency 'fog-core', '>= 1.45.0'
32
31
  spec.add_dependency 'fog-json', '>= 1.0.2'
32
+ spec.add_dependency 'net-ssh'
33
33
  end
@@ -75,7 +75,7 @@ module Fog
75
75
  request :list_datacenters
76
76
  request :get_datacenter
77
77
 
78
- # Datacenters
78
+ # Ssh Keys
79
79
  request :list_ssh_keys
80
80
  request :get_ssh_key
81
81
  request :create_ssh_key
@@ -128,7 +128,7 @@ module Fog
128
128
  end
129
129
 
130
130
  def endpoint
131
- 'https://api.hetzner.cloud/v1'
131
+ 'https://api.hetzner.cloud/'
132
132
  end
133
133
 
134
134
  def camelize(str)
@@ -8,7 +8,6 @@ module Fog
8
8
  attribute :command
9
9
  attribute :status
10
10
  attribute :progress
11
- # attribute :extra_volumes
12
11
  attribute :started
13
12
  attribute :finished
14
13
  attribute :resources
@@ -9,6 +9,19 @@ module Fog
9
9
  attribute :description
10
10
  attribute :location
11
11
  attribute :server_types
12
+
13
+ def location=(value)
14
+ attributes[:location] = case value
15
+ when Hash
16
+ service.locations.new(value)
17
+ when String
18
+ service.locations.all(name: value).first
19
+ when Integer
20
+ service.locations.get(value)
21
+ else
22
+ value
23
+ end
24
+ end
12
25
  end
13
26
  end
14
27
  end
@@ -81,8 +81,19 @@ module Fog
81
81
  false
82
82
  end
83
83
  else
84
+ server_id = case serverid
85
+ when Fog::Hetznercloud::Compute::Server
86
+ serverid.identity
87
+ when String
88
+ service.servers.all(name: serverid).first.identity
89
+ when Integer
90
+ serverid
91
+ else
92
+ raise Fog::Hetznercloud::Error::InvalidInputError, 'ERROR: Need Fog::Hetznercloud::Compute::Server|String|Integer'
93
+ end
94
+
84
95
  body = {
85
- server: serverid
96
+ server: server_id
86
97
  }
87
98
 
88
99
  if (floating_ip = service.floating_ip_assign_to_server(identity, body).body['floating_ip'])
@@ -8,7 +8,6 @@ module Fog
8
8
  attribute :name
9
9
  attribute :description
10
10
  attribute :country
11
- # attribute :extra_volumes
12
11
  attribute :city
13
12
  attribute :latitude
14
13
  attribute :longitude
@@ -105,7 +105,7 @@ module Fog
105
105
  end
106
106
 
107
107
  def user_data=(value)
108
- attributes[:user_data] = if value =~ /^\.?\/[^\/]+/
108
+ attributes[:user_data] = if value =~ /^(\.|~)?\/[^\/]+/
109
109
  File.read(value)
110
110
  else
111
111
  value
@@ -17,22 +17,47 @@ module Fog
17
17
  nil
18
18
  end
19
19
 
20
- def bootstrap(bootstap_timeout: 120, new_attributes: {}, ssh_keys:)
20
+ # Sets the proc used to determine the IP Address used for ssh/scp interactions.
21
+ # @example
22
+ # service.servers.bootstrap :name => "bootstrap-server",
23
+ # :image => service.flavors.first.id,
24
+ # :server_type => service.server_types.all(:name => 'cx11').identity,
25
+ # :private_key_path => "~/.ssh/fog_rsa",
26
+ # :user_data => "./file",
27
+ # :location => 'nbg1',
28
+ # :ssh_keys => [ 'keyname' ],
29
+ # :bootstap_timeout => 120
30
+ #
31
+ # @note
32
+ # When booting without ssh_keys set, bootstrapping will wait
33
+ # until the server is read and not until you can ssh into the server
34
+ # Bootstrapping waits until bootstap_timeout seconds and then destroy
35
+ # the server. Default timeout is 120 seconds, which should be ok
36
+ # unless you have a lot of user-data scripts.
37
+ def bootstrap(new_attributes = {})
21
38
  require 'securerandom'
22
39
 
23
40
  defaults = {
24
41
  name: "hc-#{SecureRandom.hex(3)}",
25
42
  image: 'centos-7',
26
- server_type: 'cx11',
27
- ssh_keys: ssh_keys
43
+ server_type: 'cx11'
28
44
  }
29
45
 
46
+ bootstap_timeout = new_attributes[:bootstap_timeout].nil? ? 120 : new_attributes[:bootstap_timeout]
47
+
30
48
  server = create(defaults.merge(new_attributes))
31
49
 
50
+ # if ssh keys are provided wait until we can SSH the server
51
+ # othwise just wait till the server is ready as we will never
52
+ # be able to login
32
53
  status = false
33
54
  begin
34
55
  status = Timeout.timeout(bootstap_timeout) do
35
- server.wait_for { sshable?(auth_methods: %w[publickey]) }
56
+ if new_attributes[:ssh_keys].nil?
57
+ server.wait_for { ready? }
58
+ else
59
+ server.wait_for { sshable?(auth_methods: %w[publickey]) }
60
+ end
36
61
  end
37
62
  rescue Timeout::Error => e
38
63
  server.destroy
@@ -8,68 +8,8 @@ module Fog
8
8
  end
9
9
 
10
10
  class Mock
11
- def update_server(server_id, body)
12
- body = jsonify(body)
13
-
14
- server = lookup(:servers, server_id)
15
-
16
- bootscript = if body['bootscript'].is_a?(Hash)
17
- lookup(:bootscripts, body['bootscript']['id'])
18
- elsif body['bootscript'].is_a?(String)
19
- lookup(:bootscripts, body['bootscript'])
20
- end
21
-
22
- _, product_server = lookup_product_server(server['commercial_type'])
23
-
24
- if body['enable_ipv6'] && !product_server['network']['ipv6_support']
25
- raise_invalid_request_error("Cannot enable ipv6 on #{commercial_type}")
26
- end
27
-
28
- volumes = {}
29
- body['volumes'].each do |index, volume|
30
- volume = lookup(:volumes, volume['id'])
31
-
32
- if volume['server'] && volume['server']['id'] != server['id']
33
- message = "volume #{volume['id']} is already attached to a server"
34
- raise_invalid_request_error(message)
35
- end
36
-
37
- volumes[index] = volume
38
- end
39
-
40
- server['bootscript'] = bootscript if bootscript
41
- server['dynamic_ip_required'] = body['dynamic_ip_required']
42
- server['enable_ipv6'] = body['enable_ipv6']
43
- server['hostname'] = body['name']
44
- server['modification_date'] = now
45
- server['name'] = body['name']
46
- server['tags'] = body['tags']
47
- server['volumes'] = volumes
48
-
49
- if server['dynamic_ip_required'] && server['state'] == 'running'
50
- server['public_ip'] ||= create_dynamic_ip
51
- elsif !server['dynamic_ip_required'] && server['public_ip'] && server['public_ip']['dynamic']
52
- server['public_ip'] = nil
53
- end
54
-
55
- if server['enable_ipv6'] && server['state'] == 'running'
56
- server['ipv6'] ||= create_ipv6
57
- elsif !server['enable_ipv6']
58
- server['ipv6'] = nil
59
- end
60
-
61
- data[:volumes].each do |id, volume|
62
- if server['volumes'].any? { |_i, v| v['id'] == id }
63
- volume['server'] = {
64
- 'id' => server['id'],
65
- 'name' => server['name']
66
- }
67
- elsif volume['server'] && volume['server']['id'] == server['id']
68
- volume['server'] = nil
69
- end
70
- end
71
-
72
- response(status: 200, body: { 'server' => server })
11
+ def update_server(_server_id, _body)
12
+ Fog::Mock.not_implemented
73
13
  end
74
14
  end
75
15
  end
@@ -1,5 +1,5 @@
1
1
  module Fog
2
2
  module Hetznercloud
3
- VERSION = '0.0.1'.freeze
3
+ VERSION = '0.0.2'.freeze
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fog-hetznercloud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Heinzmann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-03-06 00:00:00.000000000 Z
11
+ date: 2018-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -53,7 +53,7 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: net-ssh
56
+ name: pry
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
@@ -67,7 +67,7 @@ dependencies:
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry
70
+ name: rake
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
@@ -81,7 +81,7 @@ dependencies:
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: rubocop
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
@@ -95,7 +95,7 @@ dependencies:
95
95
  - !ruby/object:Gem::Version
96
96
  version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
- name: rubocop
98
+ name: simplecov
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,7 +109,7 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: simplecov
112
+ name: bump
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
@@ -150,6 +150,20 @@ dependencies:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: 1.0.2
153
+ - !ruby/object:Gem::Dependency
154
+ name: net-ssh
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :runtime
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
153
167
  description: Fog provider gem to support the Hetzner Cloud.
154
168
  email:
155
169
  - reg@elconas.de
@@ -163,6 +177,10 @@ files:
163
177
  - Rakefile
164
178
  - bin/console
165
179
  - bin/setup
180
+ - examples/Gemfile
181
+ - examples/integrationtest.rb
182
+ - examples/keys/.dummy
183
+ - examples/userdata.txt
166
184
  - fog-hetznercloud.gemspec
167
185
  - lib/fog/hetznercloud.rb
168
186
  - lib/fog/hetznercloud/client.rb