vmfloaty 0.7.9 → 0.8.0
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 +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
|