diff --git a/Gemfile b/Gemfile
index 6c10f3a..faec794 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,5 +1,6 @@
source 'https://rubygems.org'
-gem 'thor'
+gem 'commander'
+gem 'faraday'
gemspec
diff --git a/README.md b/README.md
index 77d10a9..c60aaba 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,9 @@
vmfloaty
========
-A CLI helper tool for Puppet Labs vmpooler to help you stay afloat.
+A CLI helper tool for [Puppet Labs vmpooler](https://github.com/puppetlabs/vmpooler) to help you stay afloat.
+
+
## Install
@@ -13,14 +15,43 @@ gem install vmfloaty
## Usage
-_note:_ subject to change
+```
+ delete Schedules the deletion of a host or hosts
+ get Gets a vm or vms based on the os flag
+ help Display global or [command] help documentation
+ list Shows a list of available vms from the pooler
+ modify Modify a vms tags and TTL
+ query Get information about a given vm
+ revert Reverts a vm to a specified snapshot
+ snapshot Takes a snapshot of a given vm
+ status Prints the status of vmpooler
+ summary Prints the summary of vmpooler
+ token Retrieves or deletes a token
+ GLOBAL OPTIONS:
+
+ -h, --help
+ Display help documentation
+
+ -v, --version
+ Display version information
+
+ -t, --trace
+ Display backtrace when an error occurs
```
-Commands:
- floaty get # Gets a VM
- floaty help [COMMAND] # Describe available commands or one specific command
- floaty list [PATTERN] # List all open VMs
- floaty modify # (TODO STILL) Modify a VM
- floaty release [--all] # Schedules a VM for deletion
- floaty status # (TODO STILL) List status of all active VMs
+
+### vmfloaty dotfile
+
+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:
+
+```yaml
+#file at /Users/me/.vmpooler.yml
+url: 'http://vmpooler.mycompany.net'
+user: 'brian'
```
+
+Now vmfloaty will use those config files if no flag was specified.
+
+## vmpooler API
+
+This cli tool uses the [vmpooler API](https://github.com/puppetlabs/vmpooler/blob/master/API.md).
diff --git a/bin/floaty b/bin/floaty
index 5c5a3f6..6fe5b10 100755
--- a/bin/floaty
+++ b/bin/floaty
@@ -1,6 +1,7 @@
#!/usr/bin/env ruby
+
$LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
require 'vmfloaty'
-Vmfloaty.new(ENV).start
+Vmfloaty.new.run
diff --git a/lib/vmfloaty.rb b/lib/vmfloaty.rb
index da12900..b7e104c 100644
--- a/lib/vmfloaty.rb
+++ b/lib/vmfloaty.rb
@@ -1,17 +1,252 @@
-require 'vmfloaty/cli'
-require 'vmfloaty/hosts'
+#!/usr/bin/env ruby
+
+require 'rubygems'
+require 'commander'
+require 'yaml'
+require 'vmfloaty/auth'
+require 'vmfloaty/pooler'
class Vmfloaty
+ include Commander::Methods
- def initialize(env)
- $vmpooler_url = env['VMPOOLER_URL']
+ def run
+ program :version, '0.2.0'
+ program :description, 'A CLI helper tool for Puppet Labs vmpooler to help you stay afloat'
- unless $vmpooler_url
- $vmpooler_url = 'http://vcloud.delivery.puppetlabs.net'
+ config = read_config
+
+ command :get do |c|
+ c.syntax = 'floaty get [options]'
+ c.summary = 'Gets a vm or vms based on the os flag'
+ c.description = ''
+ c.example 'Gets 3 vms', 'floaty get --user brian --url http://vmpooler.example.com --os centos,centos,debian'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--user STRING', String, 'User to authenticate with'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.option '--token STRING', String, 'Token for vmpooler'
+ c.option '--os STRING', String, 'Operating systems to retrieve'
+ c.option '--notoken', 'Makes a request without a token'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ token = options.token || config['token']
+ user = options.user ||= config['user']
+ url = options.url ||= config['url']
+ os_types = options.os
+ no_token = options.notoken
+
+ unless no_token.nil?
+ response = Pooler.retrieve(verbose, os_types, token, url)
+ puts response
+ return
+ end
+
+ unless options.token
+ pass = password "Enter your password please:", '*'
+ token = Auth.get_token(verbose, url, user, pass)
+ end
+
+ unless os_types.nil?
+ response = Pooler.retrieve(verbose, os_types, token, url)
+ puts response
+ else
+ puts 'You did not provide an OS to get'
+ end
+ end
end
+
+ command :list do |c|
+ c.syntax = 'floaty list [options]'
+ c.summary = 'Shows a list of available vms from the pooler'
+ c.description = ''
+ c.example 'Filter the list on centos', 'floaty list --filter centos --url http://vmpooler.example.com'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--filter STRING', String, 'A filter to apply to the list'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ filter = options.filter
+ url = options.url ||= config['url']
+
+ os_list = Pooler.list(verbose, url, filter)
+ puts os_list
+ end
+ end
+
+ command :query do |c|
+ c.syntax = 'floaty query [options]'
+ c.summary = 'Get information about a given vm'
+ c.description = ''
+ c.example 'Get information about a sample host', 'floaty query --url http://vmpooler.example.com --host myvmhost.example.com'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.option '--host STRING', String, 'Hostname to query'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ url = options.url ||= config['url']
+ hostname = options.hostname
+
+ query = Pooler.query(verbose, url, hostname)
+ puts query
+ end
+ end
+
+ command :modify do |c|
+ c.syntax = 'floaty modify [options]'
+ c.summary = 'Modify a vms tags and TTL'
+ c.description = ''
+ c.example 'description', 'command example'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.option '--token STRING', String, 'Token for vmpooler'
+ c.option '--host STRING', String, 'Hostname to modify'
+ c.option '--lifetime INT', Integer, 'VM TTL (Integer, in hours)'
+ c.option '--tags HASH', Hash, 'free-form VM tagging'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ url = options.url ||= config['url']
+ hostname = options.hostname
+ lifetime = options.lifetime
+ tags = options.tags
+ token = options.token
+
+ res_body = Pooler.modify(verbose, url, hostname, token, lifetime, tags)
+ puts res_body
+ end
+ end
+
+ command :delete do |c|
+ c.syntax = 'floaty delete [options]'
+ c.summary = 'Schedules the deletion of a host or hosts'
+ c.description = ''
+ c.example 'Schedules the deletion of a host or hosts', 'floaty delete --hosts myhost1,myhost2 --url http://vmpooler.example.com'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--hosts STRING', String, 'Hostname(s) to delete'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ hosts = options.hosts
+ url = options.url ||= config['url']
+
+ Pool.delete(verbose, url, hosts)
+ end
+ end
+
+ command :snapshot do |c|
+ c.syntax = 'floaty snapshot [options]'
+ c.summary = 'Takes a snapshot of a given vm'
+ c.description = ''
+ c.example 'Takes a snapshot for a given host', 'floaty snapshot --url http://vmpooler.example.com --host myvm.example.com --token a9znth9dn01t416hrguu56ze37t790bl'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.option '--host STRING', String, 'Hostname to modify'
+ c.option '--token STRING', String, 'Token for vmpooler'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ url = options.url ||= config['url']
+ hostname = options.hostname
+ token = options.token
+
+ res_body = Pooler.snapshot(verbose, url, hostname, token)
+ puts res_body
+ end
+ end
+
+ command :revert do |c|
+ c.syntax = 'floaty revert [options]'
+ c.summary = 'Reverts a vm to a specified snapshot'
+ c.description = ''
+ c.example 'Reverts to a snapshot for a given host', 'floaty revert --url http://vmpooler.example.com --host myvm.example.com --token a9znth9dn01t416hrguu56ze37t790bl --snapshot n4eb4kdtp7rwv4x158366vd9jhac8btq'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.option '--host STRING', String, 'Hostname to modify'
+ c.option '--token STRING', String, 'Token for vmpooler'
+ c.option '--snapshot STRING', String, 'SHA of snapshot'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ url = options.url ||= config['url']
+ hostname = options.hostname
+ token = options.token
+ snapshot_sha = options.snapshot
+
+ res_body = Pooler.revert(verbose, url, hostname, token, snapshot_sha)
+ puts res_body
+ end
+ end
+
+ command :status do |c|
+ c.syntax = 'floaty status [options]'
+ c.summary = 'Prints the status of vmpooler'
+ c.description = ''
+ c.example 'Gets the current vmpooler status', 'floaty status --url http://vmpooler.example.com'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ url = options.url ||= config['url']
+
+ status = Pooler.status(verbose, url)
+ puts status
+ end
+ end
+
+ command :summary do |c|
+ c.syntax = 'floaty summary [options]'
+ c.summary = 'Prints the summary of vmpooler'
+ c.description = ''
+ c.example 'Gets the current day summary of vmpooler', 'floaty summary --url http://vmpooler.example.com'
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ url = options.url ||= config['url']
+
+ summary = Pooler.summary(verbose, url)
+ puts summary
+ end
+ end
+
+ command :token do |c|
+ c.syntax = 'floaty token [get | delete | status]'
+ c.summary = 'Retrieves or deletes a token'
+ c.description = ''
+ c.example '', ''
+ c.option '--verbose', 'Enables verbose output'
+ c.option '--url STRING', String, 'URL of vmpooler'
+ c.option '--user STRING', String, 'User to authenticate with'
+ c.option '--token STRING', String, 'Token for vmpooler'
+ c.action do |args, options|
+ verbose = options.verbose || config['verbose']
+ action = args.first
+ url = options.url ||= config['url']
+ token = options.token
+ user = options.user ||= config['user']
+ pass = password "Enter your password please:", '*'
+
+ case action
+ when "get"
+ puts Auth.get_token(verbose, url, user, pass)
+ when "delete"
+ Auth.delete_token(verbose, url, user, pass, token)
+ when "status"
+ Auth.token_status(verbose, url, user, pass, token)
+ when nil
+ STDERR.puts "No action provided"
+ else
+ STDERR.puts "Unknown action: #{action}"
+ end
+ end
+ end
+
+ run!
end
- def start
- CLI.start(ARGV)
+ def read_config
+ conf = {}
+ begin
+ conf = YAML.load_file("#{Dir.home}/.vmfloaty.yml")
+ rescue
+ STDERR.puts "There was no config file at #{Dir.home}/.vmfloaty.yml"
+ end
+ conf
end
end
diff --git a/lib/vmfloaty/auth.rb b/lib/vmfloaty/auth.rb
new file mode 100644
index 0000000..4816e04
--- /dev/null
+++ b/lib/vmfloaty/auth.rb
@@ -0,0 +1,59 @@
+require 'faraday'
+require 'json'
+require 'vmfloaty/http'
+
+class Auth
+ def self.get_token(verbose, url, user, password)
+ conn = Http.get_conn_with_auth(verbose, url, user, password)
+
+ resp = conn.post "/token"
+
+ res_body = JSON.parse(resp.body)
+ if res_body["ok"]
+ return res_body["token"]
+ else
+ STDERR.puts "There was a problem with your request:"
+ puts res_body
+ exit 1
+ end
+ end
+
+ def self.delete_token(verbose, url, user, password, token)
+ if token.nil?
+ STDERR.puts 'You did not provide a token'
+ exit 1
+ end
+
+ conn = Http.get_conn_with_auth(verbose, url, user, password)
+
+ response = conn.delete "/token/#{token}"
+ res_body = JSON.parse(response.body)
+ if res_body["ok"]
+ puts res_body
+ else
+ STDERR.puts "There was a problem with your request:"
+ puts res_body
+ exit 1
+ end
+ end
+
+ def self.token_status(verbose, url, user, password, token)
+ if token.nil?
+ STDERR.puts 'You did not provide a token'
+ exit 1
+ end
+
+ conn = Http.get_conn_with_auth(verbose, url, user, password)
+
+ response = conn.get "/token/#{token}"
+ res_body = JSON.parse(response.body)
+
+ if res_body["ok"]
+ puts res_body
+ else
+ STDERR.puts "There was a problem with your request:"
+ puts res_body
+ exit 1
+ end
+ end
+end
diff --git a/lib/vmfloaty/cli.rb b/lib/vmfloaty/cli.rb
deleted file mode 100644
index 5b41181..0000000
--- a/lib/vmfloaty/cli.rb
+++ /dev/null
@@ -1,87 +0,0 @@
-require 'thor'
-require 'net/http'
-require 'uri'
-require 'json'
-
-class CLI < Thor
- desc "get ", "Gets a VM"
- def get(os_list)
- # HTTP POST -d os_list vmpooler.company.com/vm
-
- uri = URI.parse("#{$vmpooler_url}/vm/#{os_list.gsub(",","+")}")
- http = Net::HTTP.new(uri.host, uri.port)
- request = Net::HTTP::Post.new(uri.request_uri)
- response = http.request(request)
-
- if response.code.to_i == 200
- hosts = JSON.parse(response.body)
-
- # puts hosts
-
- save_hosts = {}
- hosts.each do |k,v|
- unless k == 'ok' || k == 'domain'
- save_hosts[k] = v['hostname']
- end
- end
-
- puts 'New Hosts:'
- puts save_hosts
-
- #hosts.add_host save_hosts
- end
-
- # parse host names/os's and save
- end
-
- desc "modify ", "Modify a VM"
- def modify(hostname)
- say 'Modify a vm'
- end
-
- desc "status", "List status of all active VMs"
- def status
- #$hosts.print_host_list
- end
-
- desc "list [PATTERN]", "List all open VMs"
- def list(pattern=nil)
- # HTTP GET vmpooler.company.com/vm
- uri = URI.parse("#{$vmpooler_url}/vm")
- response = Net::HTTP.get_response(uri)
- host_res = JSON.parse(response.body)
-
- if pattern
- # Filtering VMs based on pattern
- hosts = host_res.select { |i| i[/#{pattern}/] }
- else
- hosts = host_res
- end
-
- puts hosts
- end
-
- desc "release [--all]", "Schedules a VM for deletion"
- option :all
- def release(hostname_list=nil)
- # HTTP DELETE vmpooler.company.com/vm/#{hostname}
- # { "ok": true }
-
- if options[:all]
- # release all hosts managed by vmfloaty
- else
- hostname_arr = hostname_list.split(',')
-
- hostname_arr.each do |hostname|
- say "Releasing host #{hostname}..."
- uri = URI.parse("#{$vmpooler_url}/vm/#{hostname}")
- http = Net::HTTP.new(uri.host, uri.port)
- request = Net::HTTP::Delete.new(uri.request_uri)
- response = http.request(request)
- res = JSON.parse(response.body)
-
- puts res
- end
- end
- end
-end
diff --git a/lib/vmfloaty/hosts.rb b/lib/vmfloaty/hosts.rb
deleted file mode 100644
index 5ca90a5..0000000
--- a/lib/vmfloaty/hosts.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# manage hosts used from vm pooler
-class Hosts
- def initialize
- @host_list = {}
- end
-
- def add_host(host_hash)
- host_hash.each do |k,v|
- if @host_list[k]
- @host_list[k].push(v)
- else
- if v.is_a?(Array)
- @host_list[k] = v
- else
- @host_list[k] = [v]
- end
- end
- end
-
- puts @host_list
- end
-
- def remove_host(host_id)
- end
-
- def print_host_list
- puts @host_list
- end
-end
diff --git a/lib/vmfloaty/http.rb b/lib/vmfloaty/http.rb
new file mode 100644
index 0000000..ecc6b2f
--- /dev/null
+++ b/lib/vmfloaty/http.rb
@@ -0,0 +1,60 @@
+require 'faraday'
+
+class Http
+ def self.get_conn(verbose, url)
+ if url.nil?
+ STDERR.puts "The url you provided was empty"
+ exit 1
+ end
+
+ conn = Faraday.new(:url => url, :ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ faraday.response :logger if verbose
+ faraday.adapter Faraday.default_adapter
+ end
+
+ return conn
+ end
+
+ def self.get_conn_with_auth(verbose, url, user, password)
+ if url.nil?
+ STDERR.puts "The url you provided was empty"
+ exit 1
+ end
+
+ if user.nil?
+ STDERR.puts "You did not provide a user to authenticate with"
+ exit 1
+ end
+
+ conn = Faraday.new(:url => url, :ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ faraday.request :basic_auth, user, password
+ faraday.response :logger if verbose
+ faraday.adapter Faraday.default_adapter
+ end
+
+ return conn
+ end
+
+ def self.get_conn_with_token(verbose, url, token)
+ if url.nil?
+ STDERR.puts "The url you provided was empty"
+ exit 1
+ end
+
+ if token.nil?
+ STDERR.puts "The token you provided was empty"
+ exit 1
+ end
+
+ conn = Faraday.new(:url => url, :ssl => {:verify => false}) do |faraday|
+ faraday.request :url_encoded
+ faraday.request :token_auth, token
+ faraday.response :logger if verbose
+ faraday.adapter Faraday.default_adapter
+ end
+
+ return conn
+ end
+end
diff --git a/lib/vmfloaty/pooler.rb b/lib/vmfloaty/pooler.rb
new file mode 100644
index 0000000..25b79c7
--- /dev/null
+++ b/lib/vmfloaty/pooler.rb
@@ -0,0 +1,100 @@
+require 'faraday'
+require 'vmfloaty/http'
+require 'json'
+
+class Pooler
+ def self.list(verbose, url, os_filter=nil)
+ conn = Http.get_conn(verbose, url)
+
+ response = conn.get '/vm'
+ response_body = JSON.parse(response.body)
+
+ if os_filter
+ hosts = response_body.select { |i| i[/#{os_filter}/] }
+ else
+ hosts = response_body
+ end
+
+ hosts
+ end
+
+ def self.retrieve(verbose, os_type, token, url)
+ os = os_type.gsub(',','+')
+ if token.nil?
+ conn = Http.get_conn(verbose, url)
+ else
+ conn = Http.get_conn_with_token(verbose, url, token)
+ conn.headers['X-AUTH-TOKEN']
+ end
+
+ response = conn.post "/vm/#{os}"
+
+ res_body = JSON.parse(response.body)
+ res_body
+ end
+
+ def self.modify(verbose, url, hostname, token, lifetime, tags)
+ modify_body = {'lifetime'=>lifetime, 'tags'=>tags}
+ conn = Http.get_conn_with_token(verbose, url, token)
+
+ response = conn.put do |req|
+ req.url "/vm/#{hostname}"
+ end
+ res_body = JSON.parse(response.body)
+
+ res_body
+ end
+
+ def self.delete(verbose, url, hostname)
+ hosts = hostnames.split(',')
+ conn = Http.get_conn(verbose, url)
+
+ hosts.each do |host|
+ puts "Scheduling host #{host} for deletion"
+ response = conn.delete "/vm/#{host}"
+ res_body = JSON.parse(response.body)
+ puts res_body
+ end
+ end
+
+ def self.status(verbose, url)
+ conn = Http.get_conn(verbose, url)
+
+ response = conn.get '/status'
+ res_body = JSON.parse(response.body)
+ res_body
+ end
+
+ def self.summary(verbose, url)
+ conn = Http.get_conn(verbose, url)
+
+ response = conn.get '/summary'
+ res_body = JSON.parse(response.body)
+ res_body
+ end
+
+ def self.query(verbose, url, hostname)
+ conn = Http.get_conn(verbose, url)
+
+ response = conn.get "/vm/#{hostname}"
+ res_body = JSON.parse(response.body)
+
+ res_body
+ end
+
+ def self.snapshot(verbose, url, hostname, token)
+ conn = Http.get_conn_with_token(verbose, url, token)
+
+ response = conn.post "/vm/#{hostname}/snapshot"
+ res_body = JSON.parse(response.body)
+ res_body
+ end
+
+ def self.revert(verbose, url, hostname, token, snapshot_sha)
+ conn = Http.get_conn_with_token(verbose, url, token)
+
+ response = conn.post "/vm/#{hostname}/snapshot/#{snapshot}"
+ res_body = JSON.parse(response.body)
+ res_body
+ end
+end
diff --git a/lib/vmfloaty/version.rb b/lib/vmfloaty/version.rb
deleted file mode 100644
index 7da0994..0000000
--- a/lib/vmfloaty/version.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-module Vmfloaty
- module CLI
- VERSION = '0.1.0'
- end
-end
diff --git a/vmfloaty.gemspec b/vmfloaty.gemspec
index f20500b..c00e709 100644
--- a/vmfloaty.gemspec
+++ b/vmfloaty.gemspec
@@ -1,8 +1,6 @@
-require File.expand_path '../lib/vmfloaty/version', __FILE__
-
Gem::Specification.new do |s|
s.name = 'vmfloaty'
- s.version = Vmfloaty::CLI::VERSION.dup
+ s.version = '0.2.0'
s.authors = ['Brian Cain']
s.email = ['brian.cain@puppetlabs.com']
s.license = 'Apache'
@@ -13,5 +11,4 @@ Gem::Specification.new do |s|
s.files = Dir['LICENSE', 'README.md', 'lib/**/*']
s.test_files = Dir['spec/**/*']
s.require_path = 'lib'
- s.add_dependency 'thor', '~> 0.19'
end
diff --git a/vmfloaty.yml.example b/vmfloaty.yml.example
new file mode 100644
index 0000000..7728537
--- /dev/null
+++ b/vmfloaty.yml.example
@@ -0,0 +1,2 @@
+url: 'http://vmpooler.example.com'
+user: 'brian'