hetzner-cli 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ Gemfile.lock
2
+ pkg/*
data/.rvmrc ADDED
@@ -0,0 +1,6 @@
1
+ #this uses rvm
2
+ rvm use 1.8.7
3
+ rvm_gemset_create_on_use_flag=1
4
+ rvm gemset use hetzner-cli
5
+ alias rake="bundle exec rake"
6
+ alias hetzner-cli="bin/hetzner-cli"
data/Gemfile ADDED
@@ -0,0 +1,7 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "hetzner-cli", :path => '.'
4
+ gem "faraday"
5
+ gem "rake"
6
+
7
+ gemspec
data/README.md ADDED
@@ -0,0 +1,53 @@
1
+ # Hetnzer CLI
2
+ ## Purpose
3
+ [Hetzner](http://www.hetzner.de) is a hoster that allows you to get 'real' physical machines instead of all the 'virtualized' cloud stuff.
4
+ This is especially handy for testing virtualization stuff.
5
+
6
+ They provide a 'REST' like api that you can use to call separate tasks , like reset a server, install linux etc..
7
+ For most of the simple tasks,one could get away with using 'curl', but some tasks (like re-installing a server) require more coordination
8
+
9
+ 1. purpose #1 : of this is to automate these 'combined' hetnzer tasks via CLI instead of using their webinterface.
10
+ 2. purpose #2 : me to learn better their api and potentially put this into [fog](http://fog.io)
11
+ 3. purpose #3 : extend [mccloud](http://github.com/jedi4ever/mccloud) to allow for Hetnzer support
12
+
13
+ ## Tasks implemented
14
+
15
+ ### Distributions
16
+
17
+ Usage:
18
+ hetzner-cli distributions IP --password=PASSWORD --user=USER
19
+
20
+ Options:
21
+ --user=USER # Hetzner Admin Username
22
+ --password=PASSWORD # Hetzner Admin Password
23
+ [--robot-url=ROBOT_URL] # URL to connect to hetzner robo service
24
+ # Default: https://robot-ws.your-server.de/
25
+
26
+ List availble distributions for IP
27
+
28
+ ### Kickstart
29
+ The tasks of re-installing a server from scratch and putting an initial ssh key on it
30
+
31
+ Usage:
32
+ hetzner-cli kickstart IP --dist=DIST --password=PASSWORD --user=USER
33
+
34
+ Options:
35
+ [--lang=LANG] # Architecture to use
36
+ # Default: en
37
+ [--arch=ARCH] # Architecture to use (32|64)
38
+ # Default: 64
39
+ --user=USER # Hetzner Admin Username
40
+ --password=PASSWORD # Hetzner Admin Password
41
+ [--robot-url=ROBOT_URL] # URL to connect to hetzner robo service
42
+ # Default: https://robot-ws.your-server.de/
43
+ [--key-file=KEY_FILE] # SSH key to install as root user
44
+ # Default: /Users/patrick/.ssh/id_dsa.pub
45
+ --dist=DIST # Distribution to use
46
+
47
+ Re-install server with IP
48
+
49
+ ## Todo
50
+
51
+ - obviously make it catch errors more and write tests
52
+ - potentially integrate the functionality into fog with a hetzner provider
53
+ - look into using the hetzner-api plugin to leverage all the API calls
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ require 'bundler/setup'
4
+
5
+ require 'rake/testtask'
6
+ Bundler::GemHelper.install_tasks
data/bin/hetzner-cli ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ require 'hetzner-cli'
3
+
4
+ # Disable color if the proper argument was passed
5
+ shell = ARGV.include?("--no-color") ? Thor::Shell::Basic.new : Thor::Base.shell.new
6
+
7
+ # Start the CLI
8
+ ::HetznerCli::CLI.start(ARGV)
@@ -0,0 +1,30 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path("../lib/hetzner-cli/version", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "hetzner-cli"
6
+ s.version = HetznerCli::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.authors = ["Patrick Debois"]
9
+ s.email = ["patrick.debois@jedi.be"]
10
+ s.homepage = "http://github.com/jedi4ever/hetzner-cli/"
11
+ s.summary = %q{Manage a Hetzner machine}
12
+ s.description = %q{Manage a Hetzner machine}
13
+
14
+ s.required_rubygems_version = ">= 1.3.6"
15
+ s.rubyforge_project = "hetzner-cli"
16
+
17
+ s.add_dependency "thor"
18
+ s.add_dependency "yajl-ruby"
19
+ s.add_dependency "json"
20
+ s.add_dependency "excon"
21
+ s.add_dependency "net-ssh"
22
+ s.add_dependency "system_timer"
23
+
24
+ s.add_development_dependency "bundler", ">= 1.0.0"
25
+
26
+ s.files = `git ls-files`.split("\n")
27
+ s.executables = `git ls-files`.split("\n").map{ |f| f =~ /^bin\/(.*)/ ? $1 : nil }.compact
28
+ s.require_path = 'lib'
29
+ end
30
+
@@ -0,0 +1,3 @@
1
+ require 'thor'
2
+
3
+ require 'hetzner-cli/cli'
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+
3
+ require 'hetzner-cli/command'
4
+
5
+ module HetznerCli
6
+ class CLI < Thor
7
+
8
+ include HetznerCli::Command
9
+
10
+ desc "kickstart IP", "Re-install server with IP"
11
+ method_option :robot_url , :default => 'https://robot-ws.your-server.de/', :desc => "URL to connect to hetzner robo service"
12
+ method_option :user, :desc => 'Hetzner Admin Username', :required => true
13
+ method_option :password, :desc => 'Hetzner Admin Password', :required => true
14
+ method_option :dist, :desc => "Distribution to use", :required => true
15
+ method_option :arch, :default => '64', :desc => "Architecture to use (32|64)"
16
+ method_option :key_file, :default => File.join(ENV['HOME'],'.ssh','id_dsa.pub'), :desc => "SSH key to install as root user"
17
+ method_option :lang, :default => 'en', :desc => "Architecture to use"
18
+
19
+ def kickstart(ip)
20
+ _kickstart(ip,options)
21
+ end
22
+
23
+ desc "distributions IP", "List availble distributions for IP"
24
+ method_option :robot_url , :default => 'https://robot-ws.your-server.de/', :desc => "URL to connect to hetzner robo service"
25
+ method_option :user, :desc => 'Hetzner Admin Username', :required => true
26
+ method_option :password, :desc => 'Hetzner Admin Password', :required => true
27
+
28
+ def distributions(ip)
29
+ _distributions(ip,options)
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ require 'hetzner-cli/command/kickstart'
2
+ require 'hetzner-cli/command/distributions'
3
+
4
+ module HetznerCli
5
+ module Command
6
+ include Kickstart
7
+ include Distributions
8
+ end
9
+ end
@@ -0,0 +1,40 @@
1
+ module HetznerCli
2
+ module Distributions
3
+
4
+ require 'pp'
5
+ require 'faraday'
6
+ require 'json'
7
+ require 'net/ssh'
8
+
9
+ def _distributions(ip,options)
10
+ user = options['user']
11
+ password = options['password']
12
+ robot_url = options['robot_url']
13
+
14
+ # Create connection
15
+ conn = Faraday.new(:url => robot_url) do |faraday|
16
+ faraday.request :url_encoded # form-encode POST params
17
+ #faraday.response :logger # log requests to STDOUT
18
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
19
+ end
20
+
21
+ # Set credentials
22
+ conn.basic_auth(user,password)
23
+
24
+ begin
25
+ # Get a list of available distributions
26
+ puts "[#{ip}] Available distributions:"
27
+ response = conn.get("/boot/#{ip}")
28
+ boot_info = JSON.parse(response.body)
29
+ distributions = boot_info['boot']['linux']['dist']
30
+ distributions.each do |distro|
31
+ puts "[#{ip}] - #{distro}"
32
+ end
33
+ rescue Faraday::Error::ConnectionFailed => ex
34
+ $stderr.puts "Error logging in #{ex}"
35
+ end
36
+
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,116 @@
1
+ module HetznerCli
2
+ module Kickstart
3
+
4
+ require 'pp'
5
+ require 'faraday'
6
+ require 'json'
7
+ require 'net/ssh'
8
+
9
+ def _kickstart(ip,options)
10
+ user = options['user']
11
+ password = options['password']
12
+ robot_url = options['robot_url']
13
+ dist = options['dist']
14
+ lang = options['lang']
15
+ arch = options['arch']
16
+ key_file = options['key_file']
17
+ key = ''
18
+
19
+ # Reading keyfile
20
+ begin
21
+ key = File.read(key_file)
22
+ rescue Error => ex
23
+ $stderr.puts "[#{ip}] Error reading key_file"
24
+ exit -1
25
+ end
26
+
27
+ # Create connection
28
+ conn = Faraday.new(:url => robot_url) do |faraday|
29
+ faraday.request :url_encoded # form-encode POST params
30
+ #faraday.response :logger # log requests to STDOUT
31
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
32
+ end
33
+
34
+ # Set credentials
35
+ conn.basic_auth(user,password)
36
+
37
+ # Check if new installation was already requested
38
+ unless installing?(conn,ip)
39
+
40
+ # Get a list of available distributions
41
+ puts "[#{ip}] Available distributions:"
42
+ response = conn.get("/boot/#{ip}")
43
+ boot_info = JSON.parse(response.body)
44
+ distributions = boot_info['boot']['linux']['dist']
45
+ distributions.each do |distro|
46
+ puts "[#{ip}] - #{distro}"
47
+ end
48
+
49
+ # Bail out if the distribution specified is not listed
50
+ unless distributions.include?(dist)
51
+ $stderr.puts "[#{ip}] The specified distribution '#{dist}' is not available for server with"
52
+ exit -1
53
+ else
54
+ puts "[#{ip}] Distribution selected: #{dist}"
55
+ end
56
+
57
+ # Trigger a new linux install on reboot
58
+ puts "[#{ip}] Activating new linux install: distribution '#{dist}', arch '#{arch}', lang '#{lang}'"
59
+ response = conn.post("/boot/#{ip}/linux", { :dist => dist , :arch => arch , :lang => lang})
60
+ linux_info = JSON.parse(response.body)
61
+ new_password = linux_info['linux']['password']
62
+
63
+ puts "[#{ip}] Sending hw reset"
64
+ # Hardware reboot the system
65
+ response = conn.post("/reset/#{ip}", { :type => 'hw'})
66
+
67
+ # Allowing the system to go down, saves us from checking bad authentication errors
68
+ puts "[#{ip}] New Password : #{new_password}"
69
+
70
+ begin
71
+ fully_booted = false
72
+ print "[#{ip}] Waiting for the linux install to finish and reboot: "
73
+ STDOUT.flush
74
+ 4.times {
75
+ sleep 5
76
+ print '.'
77
+ STDOUT.flush
78
+ }
79
+ while !fully_booted do
80
+ sleep 5
81
+ print '.'
82
+ STDOUT.flush
83
+ begin
84
+ # Ignoring new key
85
+ Net::SSH.start(ip,'root',:password => new_password , :paranoid => false, :timeout => 5 ) do |ssh|
86
+ output = ssh.exec!("cat /root/.ssh/authorized_keys2")
87
+ if output.include?("No such file")
88
+ puts
89
+ puts "[#{ip}] Install is finished and system is available"
90
+ puts "[#{ip}] Installing root ssh key"
91
+ output = ssh.exec!("echo '#{key}' > /root/.ssh/authorized_keys")
92
+ fully_booted = true
93
+ end
94
+ end
95
+ rescue Net::SSH::Disconnect,Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ECONNABORTED, Errno::ECONNRESET, Errno::ENETUNREACH,Errno::ETIMEDOUT,IOError,Timeout::Error,Net::SSH::AuthenticationFailed
96
+ #Ignoring these errors
97
+ end
98
+ end
99
+ end
100
+ puts "[#{ip}] System is ready for login"
101
+ puts "[#{ip}] You might want to cleanup your old key:"
102
+ puts "[#{ip}] ssh-keygen -R #{ip}"
103
+ puts "[#{ip}] ssh-keyscan #{ip} >> $HOME/.ssh/known_hosts"
104
+ else
105
+ $stderr.puts "[#{ip}] Installation already in progress, aborting"
106
+ exit -1
107
+ end
108
+ end
109
+
110
+ def installing?(conn,ip)
111
+ response = conn.get("/boot/#{ip}/linux")
112
+ linux_info = JSON.parse(response.body)
113
+ return linux_info['linux']['active'] == true
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,3 @@
1
+ module HetznerCli
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,179 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hetzner-cli
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 2
10
+ version: 0.0.2
11
+ platform: ruby
12
+ authors:
13
+ - Patrick Debois
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-08-15 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ requirement: &id001 !ruby/object:Gem::Requirement
22
+ none: false
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ hash: 3
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :runtime
31
+ name: thor
32
+ version_requirements: *id001
33
+ prerelease: false
34
+ - !ruby/object:Gem::Dependency
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ none: false
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ hash: 3
41
+ segments:
42
+ - 0
43
+ version: "0"
44
+ type: :runtime
45
+ name: yajl-ruby
46
+ version_requirements: *id002
47
+ prerelease: false
48
+ - !ruby/object:Gem::Dependency
49
+ requirement: &id003 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ type: :runtime
59
+ name: json
60
+ version_requirements: *id003
61
+ prerelease: false
62
+ - !ruby/object:Gem::Dependency
63
+ requirement: &id004 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ hash: 3
69
+ segments:
70
+ - 0
71
+ version: "0"
72
+ type: :runtime
73
+ name: excon
74
+ version_requirements: *id004
75
+ prerelease: false
76
+ - !ruby/object:Gem::Dependency
77
+ requirement: &id005 !ruby/object:Gem::Requirement
78
+ none: false
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ hash: 3
83
+ segments:
84
+ - 0
85
+ version: "0"
86
+ type: :runtime
87
+ name: net-ssh
88
+ version_requirements: *id005
89
+ prerelease: false
90
+ - !ruby/object:Gem::Dependency
91
+ requirement: &id006 !ruby/object:Gem::Requirement
92
+ none: false
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ hash: 3
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ type: :runtime
101
+ name: system_timer
102
+ version_requirements: *id006
103
+ prerelease: false
104
+ - !ruby/object:Gem::Dependency
105
+ requirement: &id007 !ruby/object:Gem::Requirement
106
+ none: false
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ hash: 23
111
+ segments:
112
+ - 1
113
+ - 0
114
+ - 0
115
+ version: 1.0.0
116
+ type: :development
117
+ name: bundler
118
+ version_requirements: *id007
119
+ prerelease: false
120
+ description: Manage a Hetzner machine
121
+ email:
122
+ - patrick.debois@jedi.be
123
+ executables:
124
+ - hetzner-cli
125
+ extensions: []
126
+
127
+ extra_rdoc_files: []
128
+
129
+ files:
130
+ - .gitignore
131
+ - .rvmrc
132
+ - Gemfile
133
+ - README.md
134
+ - Rakefile
135
+ - bin/hetzner-cli
136
+ - hetzner-cli.gemspec
137
+ - lib/hetzner-cli.rb
138
+ - lib/hetzner-cli/cli.rb
139
+ - lib/hetzner-cli/command.rb
140
+ - lib/hetzner-cli/command/distributions.rb
141
+ - lib/hetzner-cli/command/kickstart.rb
142
+ - lib/hetzner-cli/version.rb
143
+ homepage: http://github.com/jedi4ever/hetzner-cli/
144
+ licenses: []
145
+
146
+ post_install_message:
147
+ rdoc_options: []
148
+
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ none: false
153
+ requirements:
154
+ - - ">="
155
+ - !ruby/object:Gem::Version
156
+ hash: 3
157
+ segments:
158
+ - 0
159
+ version: "0"
160
+ required_rubygems_version: !ruby/object:Gem::Requirement
161
+ none: false
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ hash: 23
166
+ segments:
167
+ - 1
168
+ - 3
169
+ - 6
170
+ version: 1.3.6
171
+ requirements: []
172
+
173
+ rubyforge_project: hetzner-cli
174
+ rubygems_version: 1.8.12
175
+ signing_key:
176
+ specification_version: 3
177
+ summary: Manage a Hetzner machine
178
+ test_files: []
179
+