vmfloaty 0.7.9 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +67 -1
- data/lib/vmfloaty/errors.rb +6 -0
- data/lib/vmfloaty/nonstandard_pooler.rb +135 -0
- data/lib/vmfloaty/pooler.rb +20 -8
- data/lib/vmfloaty/service.rb +133 -0
- data/lib/vmfloaty/utils.rb +163 -73
- data/lib/vmfloaty/version.rb +1 -1
- data/lib/vmfloaty.rb +191 -327
- data/spec/spec_helper.rb +7 -0
- data/spec/vmfloaty/nonstandard_pooler_spec.rb +325 -0
- data/spec/vmfloaty/pooler_spec.rb +4 -3
- data/spec/vmfloaty/service_spec.rb +79 -0
- data/spec/vmfloaty/utils_spec.rb +183 -49
- metadata +10 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 17d69277c7eb6a7e91324b79897833147913c900
|
4
|
+
data.tar.gz: af3ff211ec20d651442e2277cf12b42605e6b75f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f14e92275d92c7aaca9d71d17a580588490e195adc4e192ab15f5d8149096d0fbab161adb63be404002beb2ca3a345fee99e9646d8e127fa9522da86fa2a344c
|
7
|
+
data.tar.gz: 48f146c619671dfc7f60e88e73c23f626e6a6798de1480c9209e05045830c02e4a4a3896a1164daa5f6e494883b1da73181ca1ab82c84a3b51b1bfbb6e9316d4
|
data/README.md
CHANGED
@@ -7,6 +7,8 @@ A CLI helper tool for [Puppet Labs vmpooler](https://github.com/puppetlabs/vmpoo
|
|
7
7
|
|
8
8
|
<img src="http://i.imgur.com/xGcGwuH.jpg" width=200 height=200>
|
9
9
|
|
10
|
+
This project is still supported by @briancain and @demophoon. Ping either of us if you'd like something merged and released.
|
11
|
+
|
10
12
|
## Install
|
11
13
|
|
12
14
|
Grab the latest from ruby gems...
|
@@ -66,8 +68,10 @@ floaty get centos-7-x86_64=2 debian-7-x86_64 windows-10=3 --token mytokenstring
|
|
66
68
|
|
67
69
|
If you do not wish to continuely specify various config options with the cli, you can have a dotfile in your home directory for some defaults. For example:
|
68
70
|
|
71
|
+
#### Basic configuration
|
72
|
+
|
69
73
|
```yaml
|
70
|
-
#file at /Users/me/.vmfloaty.yml
|
74
|
+
# file at /Users/me/.vmfloaty.yml
|
71
75
|
url: 'https://vmpooler.mycompany.net/api/v1'
|
72
76
|
user: 'brian'
|
73
77
|
token: 'tokenstring'
|
@@ -75,6 +79,66 @@ token: 'tokenstring'
|
|
75
79
|
|
76
80
|
Now vmfloaty will use those config files if no flag was specified.
|
77
81
|
|
82
|
+
#### Configuring multiple services
|
83
|
+
|
84
|
+
Most commands allow you to specify a `--service <servicename>` option to allow the use of multiple vmpooler instances. This can be useful when you'd rather not specify a `--url` or `--token` by hand for alternate services.
|
85
|
+
|
86
|
+
To configure multiple services, you can set up your `~/.vmfloaty.yml` config file like this:
|
87
|
+
|
88
|
+
```yaml
|
89
|
+
# file at /Users/me/.vmfloaty.yml
|
90
|
+
user: 'brian'
|
91
|
+
services:
|
92
|
+
main:
|
93
|
+
url: 'https://vmpooler.mycompany.net/api/v1'
|
94
|
+
token: 'tokenstring'
|
95
|
+
alternate:
|
96
|
+
url: 'https://vmpooler.alternate.net/api/v1'
|
97
|
+
token: 'alternate-tokenstring'
|
98
|
+
```
|
99
|
+
|
100
|
+
- If you run `floaty` without a `--service <name>` option, vmfloaty will use the first configured service by default.
|
101
|
+
With the config file above, the default would be to use the 'main' vmpooler instance.
|
102
|
+
- If keys are missing for a configured service, vmfloaty will attempt to fall back to the top-level values.
|
103
|
+
With the config file above, 'brian' will be used as the username for both configured services, since neither specifies a username.
|
104
|
+
|
105
|
+
Examples using the above configuration:
|
106
|
+
|
107
|
+
List available vm types from our main vmpooler instance:
|
108
|
+
```sh
|
109
|
+
floaty list --service main
|
110
|
+
# or, since the first configured service is used by default:
|
111
|
+
floaty list
|
112
|
+
```
|
113
|
+
|
114
|
+
List available vm types from our alternate vmpooler instance:
|
115
|
+
```sh
|
116
|
+
floaty list --service alternate
|
117
|
+
```
|
118
|
+
|
119
|
+
#### Using a Nonstandard Pooler service
|
120
|
+
|
121
|
+
vmfloaty is capable of working with Puppet's [nonstandard pooler](https://github.com/puppetlabs/nspooler) in addition to the default vmpooler API. To add a nonstandard pooler service, specify an API `type` value in your service configuration, like this:
|
122
|
+
|
123
|
+
```yaml
|
124
|
+
# file at /Users/me/.vmfloaty.yml
|
125
|
+
user: 'brian'
|
126
|
+
services:
|
127
|
+
vm:
|
128
|
+
url: 'https://vmpooler.mycompany.net/api/v1'
|
129
|
+
token: 'tokenstring'
|
130
|
+
ns:
|
131
|
+
url: 'https://nspooler.mycompany.net/api/v1'
|
132
|
+
token: 'nspooler-tokenstring'
|
133
|
+
type: 'nonstandard' # <-- 'type' is necessary for any non-vmpooler service
|
134
|
+
```
|
135
|
+
|
136
|
+
With this configuration, you could list available OS types from nspooler like this:
|
137
|
+
|
138
|
+
```sh
|
139
|
+
floaty list --service ns
|
140
|
+
```
|
141
|
+
|
78
142
|
#### Valid config keys
|
79
143
|
|
80
144
|
Here are the keys that vmfloaty currently supports:
|
@@ -87,6 +151,8 @@ Here are the keys that vmfloaty currently supports:
|
|
87
151
|
+ String
|
88
152
|
- url
|
89
153
|
+ String
|
154
|
+
- services
|
155
|
+
+ Map
|
90
156
|
|
91
157
|
### Tab Completion
|
92
158
|
|
data/lib/vmfloaty/errors.rb
CHANGED
@@ -0,0 +1,135 @@
|
|
1
|
+
require 'vmfloaty/errors'
|
2
|
+
require 'vmfloaty/http'
|
3
|
+
require 'faraday'
|
4
|
+
require 'json'
|
5
|
+
|
6
|
+
class NonstandardPooler
|
7
|
+
def self.list(verbose, url, os_filter = nil)
|
8
|
+
conn = Http.get_conn(verbose, url)
|
9
|
+
|
10
|
+
response = conn.get 'status'
|
11
|
+
response_body = JSON.parse(response.body)
|
12
|
+
os_list = response_body.keys.sort
|
13
|
+
os_list.delete 'ok'
|
14
|
+
|
15
|
+
os_filter ? os_list.select { |i| i[/#{os_filter}/] } : os_list
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.list_active(verbose, url, token)
|
19
|
+
status = Auth.token_status(verbose, url, token)
|
20
|
+
status['reserved_hosts'] || []
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.retrieve(verbose, os_type, token, url)
|
24
|
+
conn = Http.get_conn(verbose, url)
|
25
|
+
conn.headers['X-AUTH-TOKEN'] = token if token
|
26
|
+
|
27
|
+
os_string = ''
|
28
|
+
os_type.each do |os, num|
|
29
|
+
num.times do |_i|
|
30
|
+
os_string << os + '+'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
os_string = os_string.chomp('+')
|
35
|
+
|
36
|
+
if os_string.empty?
|
37
|
+
raise MissingParamError, 'No operating systems provided to obtain.'
|
38
|
+
end
|
39
|
+
|
40
|
+
response = conn.post "host/#{os_string}"
|
41
|
+
|
42
|
+
res_body = JSON.parse(response.body)
|
43
|
+
|
44
|
+
if res_body['ok']
|
45
|
+
res_body
|
46
|
+
elsif response.status == 401
|
47
|
+
raise AuthError, "HTTP #{response.status}: The token provided could not authenticate to the pooler.\n#{res_body}"
|
48
|
+
else
|
49
|
+
raise "HTTP #{response.status}: Failed to obtain VMs from the pooler at #{url}/host/#{os_string}. #{res_body}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.modify(verbose, url, hostname, token, modify_hash)
|
54
|
+
if token.nil?
|
55
|
+
raise TokenError, 'Token provided was nil; Request cannot be made to modify VM'
|
56
|
+
end
|
57
|
+
|
58
|
+
modify_hash.each do |key, value|
|
59
|
+
unless [:reason, :reserved_for_reason].include? key
|
60
|
+
raise ModifyError, "Configured service type does not support modification of #{key}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
if modify_hash[:reason]
|
65
|
+
# "reason" is easier to type than "reserved_for_reason", but nspooler needs the latter
|
66
|
+
modify_hash[:reserved_for_reason] = modify_hash.delete :reason
|
67
|
+
end
|
68
|
+
|
69
|
+
conn = Http.get_conn(verbose, url)
|
70
|
+
conn.headers['X-AUTH-TOKEN'] = token
|
71
|
+
|
72
|
+
response = conn.put do |req|
|
73
|
+
req.url "host/#{hostname}"
|
74
|
+
req.body = modify_hash.to_json
|
75
|
+
end
|
76
|
+
|
77
|
+
response.body.empty? ? {} : JSON.parse(response.body)
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.disk(verbose, url, hostname, token, disk)
|
81
|
+
raise ModifyError, 'Configured service type does not support modification of disk space'
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.snapshot(verbose, url, hostname, token)
|
85
|
+
raise ModifyError, 'Configured service type does not support snapshots'
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.revert(verbose, url, hostname, token, snapshot_sha)
|
89
|
+
raise ModifyError, 'Configured service type does not support snapshots'
|
90
|
+
end
|
91
|
+
|
92
|
+
def self.delete(verbose, url, hosts, token)
|
93
|
+
if token.nil?
|
94
|
+
raise TokenError, 'Token provided was nil; Request cannot be made to delete VM'
|
95
|
+
end
|
96
|
+
|
97
|
+
conn = Http.get_conn(verbose, url)
|
98
|
+
|
99
|
+
conn.headers['X-AUTH-TOKEN'] = token if token
|
100
|
+
|
101
|
+
response_body = {}
|
102
|
+
|
103
|
+
unless hosts.is_a? Array
|
104
|
+
hosts = hosts.split(',')
|
105
|
+
end
|
106
|
+
hosts.each do |host|
|
107
|
+
response = conn.delete "host/#{host}"
|
108
|
+
res_body = JSON.parse(response.body)
|
109
|
+
response_body[host] = res_body
|
110
|
+
end
|
111
|
+
|
112
|
+
response_body
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.status(verbose, url)
|
116
|
+
conn = Http.get_conn(verbose, url)
|
117
|
+
|
118
|
+
response = conn.get '/status'
|
119
|
+
JSON.parse(response.body)
|
120
|
+
end
|
121
|
+
|
122
|
+
def self.summary(verbose, url)
|
123
|
+
conn = Http.get_conn(verbose, url)
|
124
|
+
|
125
|
+
response = conn.get '/summary'
|
126
|
+
JSON.parse(response.body)
|
127
|
+
end
|
128
|
+
|
129
|
+
def self.query(verbose, url, hostname)
|
130
|
+
conn = Http.get_conn(verbose, url)
|
131
|
+
|
132
|
+
response = conn.get "host/#{hostname}"
|
133
|
+
JSON.parse(response.body)
|
134
|
+
end
|
135
|
+
end
|
data/lib/vmfloaty/pooler.rb
CHANGED
@@ -19,6 +19,15 @@ class Pooler
|
|
19
19
|
hosts
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.list_active(verbose, url, token)
|
23
|
+
status = Auth.token_status(verbose, url, token)
|
24
|
+
vms = []
|
25
|
+
if status[token] && status[token]['vms']
|
26
|
+
vms = status[token]['vms']['running']
|
27
|
+
end
|
28
|
+
vms
|
29
|
+
end
|
30
|
+
|
22
31
|
def self.retrieve(verbose, os_type, token, url)
|
23
32
|
# NOTE:
|
24
33
|
# Developers can use `Utils.generate_os_hash` to
|
@@ -54,25 +63,28 @@ class Pooler
|
|
54
63
|
end
|
55
64
|
end
|
56
65
|
|
57
|
-
def self.modify(verbose, url, hostname, token,
|
66
|
+
def self.modify(verbose, url, hostname, token, modify_hash)
|
58
67
|
if token.nil?
|
59
68
|
raise TokenError, "Token provided was nil. Request cannot be made to modify vm"
|
60
69
|
end
|
61
70
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
if tags
|
67
|
-
modify_body['tags'] = tags
|
71
|
+
modify_hash.keys.each do |key|
|
72
|
+
unless [:tags, :lifetime, :disk].include? key
|
73
|
+
raise ModifyError, "Configured service type does not support modification of #{key}."
|
74
|
+
end
|
68
75
|
end
|
69
76
|
|
70
77
|
conn = Http.get_conn(verbose, url)
|
71
78
|
conn.headers['X-AUTH-TOKEN'] = token
|
72
79
|
|
80
|
+
if modify_hash['disk']
|
81
|
+
disk(verbose, url, hostname, token, modify_hash['disk'])
|
82
|
+
modify_hash.delete 'disk'
|
83
|
+
end
|
84
|
+
|
73
85
|
response = conn.put do |req|
|
74
86
|
req.url "vm/#{hostname}"
|
75
|
-
req.body =
|
87
|
+
req.body = modify_hash.to_json
|
76
88
|
end
|
77
89
|
|
78
90
|
res_body = JSON.parse(response.body)
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'commander/user_interaction'
|
2
|
+
require 'commander/command'
|
3
|
+
require 'vmfloaty/utils'
|
4
|
+
require 'vmfloaty/ssh'
|
5
|
+
|
6
|
+
class Service
|
7
|
+
|
8
|
+
attr_reader :config
|
9
|
+
|
10
|
+
def initialize(options, config_hash = {})
|
11
|
+
options ||= Commander::Command::Options.new
|
12
|
+
@config = Utils.get_service_config config_hash, options
|
13
|
+
@service_object = Utils.get_service_object @config['type']
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(m, *args, &block)
|
17
|
+
if @service_object.respond_to? m
|
18
|
+
@service_object.send(m, *args, &block)
|
19
|
+
else
|
20
|
+
super
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def url
|
25
|
+
@config['url']
|
26
|
+
end
|
27
|
+
|
28
|
+
def type
|
29
|
+
@service_object.name
|
30
|
+
end
|
31
|
+
|
32
|
+
def user
|
33
|
+
unless @config['user']
|
34
|
+
puts "Enter your pooler service username:"
|
35
|
+
@config['user'] = STDIN.gets.chomp
|
36
|
+
end
|
37
|
+
@config['user']
|
38
|
+
end
|
39
|
+
|
40
|
+
def token
|
41
|
+
unless @config['token']
|
42
|
+
puts "No token found. Retrieving a token..."
|
43
|
+
@config['token'] = get_new_token(nil)
|
44
|
+
end
|
45
|
+
@config['token']
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_new_token(verbose)
|
49
|
+
username = user
|
50
|
+
pass = Commander::UI::password "Enter your pooler service password:", '*'
|
51
|
+
Auth.get_token(verbose, url, username, pass)
|
52
|
+
end
|
53
|
+
|
54
|
+
def delete_token(verbose, token_value = @config['token'])
|
55
|
+
username = user
|
56
|
+
pass = Commander::UI::password "Enter your pooler service password:", '*'
|
57
|
+
Auth.delete_token(verbose, url, username, pass, token_value)
|
58
|
+
end
|
59
|
+
|
60
|
+
def token_status(verbose, token_value)
|
61
|
+
token_value ||= @config['token']
|
62
|
+
Auth.token_status(verbose, url, token_value)
|
63
|
+
end
|
64
|
+
|
65
|
+
def list(verbose, os_filter = nil)
|
66
|
+
@service_object.list verbose, url, os_filter
|
67
|
+
end
|
68
|
+
|
69
|
+
def list_active(verbose)
|
70
|
+
@service_object.list_active verbose, url, token
|
71
|
+
end
|
72
|
+
|
73
|
+
def retrieve(verbose, os_types, use_token = true)
|
74
|
+
puts 'Requesting a vm without a token...' unless use_token
|
75
|
+
token_value = use_token ? token : nil
|
76
|
+
@service_object.retrieve verbose, os_types, token_value, url
|
77
|
+
end
|
78
|
+
|
79
|
+
def ssh(verbose, host_os, use_token = true)
|
80
|
+
token_value = nil
|
81
|
+
if use_token
|
82
|
+
begin
|
83
|
+
token_value = token || get_new_token(verbose)
|
84
|
+
rescue TokenError => e
|
85
|
+
STDERR.puts e
|
86
|
+
STDERR.puts 'Could not get token... requesting vm without a token anyway...'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
Ssh.ssh(verbose, host_os, token_value, url)
|
90
|
+
end
|
91
|
+
|
92
|
+
def pretty_print_running(verbose, hostnames = [])
|
93
|
+
if hostnames.empty?
|
94
|
+
puts "You have no running VMs."
|
95
|
+
else
|
96
|
+
puts "Running VMs:"
|
97
|
+
@service_object.pretty_print_hosts(verbose, hostnames, url)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def query(verbose, hostname)
|
102
|
+
@service_object.query verbose, url, hostname
|
103
|
+
end
|
104
|
+
|
105
|
+
def modify(verbose, hostname, modify_hash)
|
106
|
+
@service_object.modify verbose, url, hostname, token, modify_hash
|
107
|
+
end
|
108
|
+
|
109
|
+
def delete(verbose, hosts)
|
110
|
+
@service_object.delete verbose, url, hosts, token
|
111
|
+
end
|
112
|
+
|
113
|
+
def status(verbose)
|
114
|
+
@service_object.status verbose, url
|
115
|
+
end
|
116
|
+
|
117
|
+
def summary(verbose)
|
118
|
+
@service_object.summary verbose, url
|
119
|
+
end
|
120
|
+
|
121
|
+
def snapshot(verbose, hostname)
|
122
|
+
@service_object.snapshot verbose, url, hostname, token
|
123
|
+
end
|
124
|
+
|
125
|
+
def revert(verbose, hostname, snapshot_sha)
|
126
|
+
@service_object.revert verbose, url, hostname, token, snapshot_sha
|
127
|
+
end
|
128
|
+
|
129
|
+
def disk(verbose, hostname, disk)
|
130
|
+
@service_object.disk(verbose, url, hostname, token, disk)
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|