bluebox-boxcutter 0.0.14

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.
@@ -0,0 +1 @@
1
+ *.gem
@@ -0,0 +1,23 @@
1
+ ## v0.0.13 (August 22, 2013)
2
+
3
+ A rework of updating, you can now just update boxutter via 'boxcutter update' and it will
4
+ clone down the latest and greatest and update your local gem
5
+
6
+ ### Improvements
7
+ * Pull request [#32](https://github.blueboxgrid.com/bluebox-tech/boxcutter/pull/32): [update] just update - Sam
8
+
9
+ ## v0.0.12 (August 21, 2013)
10
+
11
+ You can now see if you have the latest version with: boxcutter check-for-update
12
+
13
+ ### Improvements
14
+ * Pull request [#31](https://github.blueboxgrid.com/bluebox-tech/boxcutter/pull/31): [update_avail] version check against upstream - Sam
15
+
16
+ ## v0.0.11 (August 21, 2013)
17
+
18
+ ### Improvements
19
+ * Pull request [#30](https://github.blueboxgrid.com/bluebox-tech/boxcutter/pull/30): [more_info] show host info when rebooting or razoring - Sam
20
+ * Pull request [#29](https://github.blueboxgrid.com/bluebox-tech/boxcutter/pull/29): rm hwagent token - Sam
21
+
22
+
23
+
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,51 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ bluebox-boxcutter (0.0.8)
5
+ colorize
6
+ commander
7
+ httparty
8
+ mash
9
+ nokogiri
10
+ spunkmeyer (>= 0.0.5)
11
+ terminal-table
12
+
13
+ GEM
14
+ remote: https://rubygems.org/
15
+ specs:
16
+ colorize (0.5.8)
17
+ commander (4.1.4)
18
+ highline (~> 1.6.11)
19
+ diff-lcs (1.2.4)
20
+ highline (1.6.19)
21
+ httparty (0.11.0)
22
+ multi_json (~> 1.0)
23
+ multi_xml (>= 0.5.2)
24
+ mash (0.1.1)
25
+ mini_portile (0.5.1)
26
+ multi_json (1.7.7)
27
+ multi_xml (0.5.4)
28
+ nokogiri (1.6.0)
29
+ mini_portile (~> 0.5.0)
30
+ rake (10.1.0)
31
+ rspec (2.14.1)
32
+ rspec-core (~> 2.14.0)
33
+ rspec-expectations (~> 2.14.0)
34
+ rspec-mocks (~> 2.14.0)
35
+ rspec-core (2.14.4)
36
+ rspec-expectations (2.14.0)
37
+ diff-lcs (>= 1.1.3, < 2.0)
38
+ rspec-mocks (2.14.1)
39
+ spunkmeyer (0.0.5)
40
+ colorize
41
+ sqlite3
42
+ sqlite3 (1.3.7)
43
+ terminal-table (1.4.5)
44
+
45
+ PLATFORMS
46
+ ruby
47
+
48
+ DEPENDENCIES
49
+ bluebox-boxcutter!
50
+ rake
51
+ rspec
@@ -0,0 +1,36 @@
1
+ # boxcutter
2
+
3
+ A command-line interface to BoxPanel.
4
+
5
+ # prerequisites
6
+
7
+ This tool requres `ruby --version` >= `1.9.3`.
8
+ If you are not already running a suitable version, [rbenv](https://github.com/sstephenson/rbenv) is recommended.
9
+
10
+ # installation
11
+
12
+ git clone git@github.blueboxgrid.com:bluebox-tech/boxcutter.git
13
+ cd boxcutter
14
+ gem install rspec
15
+ rake install
16
+
17
+ # usage
18
+
19
+ *You must be logged into BoxPanel in the Chrome browser - boxcutter auths with your Chrome cookies*
20
+
21
+ ```bash
22
+ boxcutter help # usage
23
+ boxcutter machine-search app # search for machine names given a substring
24
+ boxcutter machine-show ds590 # show machine details given its name
25
+ boxcutter password "kvm[2-5]" # show passwords given a password name regex
26
+ boxcutter kvm kvm9.sea03 # open a kvm console given the password name
27
+ boxcutter reboot ds590 # reboot a machine
28
+ boxcutter razor-tags # show available razor tags
29
+ boxcutter razor ds590 ubuntu-precise # razor a machine
30
+ boxcutter razor-log ds590 # show razor log messages
31
+ ```
32
+
33
+ # example
34
+
35
+ ![boxcutter screenshot](https://github.blueboxgrid.com/bluebox-tech/boxcutter/raw/master/doc/screenshot.png)
36
+
@@ -0,0 +1,14 @@
1
+ require 'rspec/core/rake_task'
2
+ $: << "#{File.dirname __FILE__}/lib"
3
+ require 'bluebox-boxcutter/version'
4
+
5
+ task :install do
6
+ sh %{gem build bluebox-boxcutter.gemspec}
7
+ sh %{gem uninstall -x bluebox-boxcutter} if system 'gem which bluebox-boxcutter'
8
+ sh %{gem install bluebox-boxcutter-#{Boxcutter::VERSION}.gem --no-ri --no-rdoc}
9
+ end
10
+
11
+ RSpec::Core::RakeTask.new(:spec) do |t|
12
+ t.pattern = Dir.glob('spec/**/*_spec.rb')
13
+ t.rspec_opts = '--color'
14
+ end
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ $:.push File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'bluebox-boxcutter'
4
+ require 'bluebox-boxcutter/cli'
@@ -0,0 +1,33 @@
1
+ #
2
+ # -*- encoding: utf-8 -*-
3
+ $: << "#{File.dirname __FILE__}/lib"
4
+ require 'bluebox-boxcutter/version'
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = 'bluebox-boxcutter'
8
+ s.version = Boxcutter::VERSION
9
+ s.platform = Gem::Platform::RUBY
10
+ s.authors = [ 'Sam Cooper', 'Tim Miller' ]
11
+ s.email = [ '' ]
12
+ s.homepage = 'https://github.blueboxgrid.com/bluebox-tech/boxcutter'
13
+ s.summary = %q{cli for boxpanel}
14
+ s.description = %q{cli for boxpanel.}
15
+
16
+ s.required_ruby_version = ">= 1.9.1"
17
+ s.required_rubygems_version = ">= 1.3.7"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ['lib']
23
+
24
+ s.add_runtime_dependency 'colorize', '>= 0'
25
+ s.add_runtime_dependency 'commander', '>= 0'
26
+ s.add_runtime_dependency 'httparty', '>= 0'
27
+ s.add_runtime_dependency 'mash', '>= 0'
28
+ s.add_runtime_dependency 'nokogiri', '>= 0'
29
+ s.add_runtime_dependency 'spunkmeyer', '>= 0.0.5'
30
+ s.add_runtime_dependency 'terminal-table', '>= 0'
31
+ s.add_development_dependency 'rake', '>= 0'
32
+ s.add_development_dependency 'rspec', '>= 0'
33
+ end
Binary file
@@ -0,0 +1,94 @@
1
+ require 'httparty'
2
+ require 'bluebox-boxcutter/kvm'
3
+ require 'bluebox-boxcutter/machine'
4
+ require 'bluebox-boxcutter/password'
5
+ require 'bluebox-boxcutter/razor'
6
+ require 'bluebox-boxcutter/ui'
7
+ require 'bluebox-boxcutter/version'
8
+ require 'bluebox-boxcutter/git'
9
+ require 'nokogiri'
10
+ require 'spunkmeyer'
11
+ require 'mash'
12
+
13
+ module Boxcutter
14
+
15
+ def self.version
16
+ eval(File.read("#{File.dirname __FILE__}/../bluebox-boxcutter.gemspec")).version.to_s
17
+ end
18
+
19
+ def self.install(git_repo)
20
+
21
+ # if we're passed a file path
22
+ if git_repo.match /^(\/.*)+\/?/
23
+ Dir.chdir git_repo
24
+ `rake install`
25
+ end
26
+ end
27
+
28
+ def self.update
29
+ Boxcutter::Git::clone
30
+ upstream_version = Boxcutter::Git::upstream_version
31
+ our_version = Boxcutter::VERSION
32
+ if Gem::Version.new(upstream_version) > Gem::Version.new(our_version)
33
+ Boxcutter::install(Boxcutter::Git::LOCAL_CLONE)
34
+ end
35
+ Boxcutter::Git::rm_clone
36
+ end
37
+
38
+ # flatten any child hashes into top level hash
39
+ # e.g. {"x" => { "a" => "b" }
40
+ # will become {"x_a" => "b" }
41
+ def self.flatten_hash(hash, parent_key = "", new_hash = { })
42
+ hash.each do |key,value|
43
+ key_string = parent_key.empty? ? key : "#{parent_key}_#{key}"
44
+ if value.class == Hash
45
+ flatten_hash(value, key_string, new_hash)
46
+ else
47
+ new_hash.merge!({ key_string => value })
48
+ end
49
+ end
50
+ new_hash
51
+ end
52
+
53
+ # a boxpanel HTTP client, which auths with cookies from one's browser.
54
+ class Boxpanel
55
+ include HTTParty
56
+
57
+ BASE_URI = 'https://boxpanel.bluebox.net/'
58
+ base_uri BASE_URI
59
+
60
+ # get cookies from browser.
61
+ def self.cookie_header
62
+ cookies = Spunkmeyer.cookies BASE_URI
63
+ cookies.to_a.map { |c| "#{c.first}=#{c.last[:value]}" }.join '; '
64
+ end
65
+
66
+ # wrap the default get method with auth and error handling.
67
+ def self.get(path, options={}, &block)
68
+ options[:headers] ||= {}
69
+ options[:headers]['Cookie'] = cookie_header
70
+ resp = super path, options, &block
71
+ raise "request failed with #{resp.code}: #{path}" unless resp.code == 200
72
+ resp.body
73
+ end
74
+
75
+ def self.get_html(path, options={}, &block)
76
+ Nokogiri::HTML Boxcutter::Boxpanel::get(path, options, &block)
77
+ end
78
+
79
+ # wrap the default post method with auth and error handling.
80
+ def self.post(path, options={}, &block)
81
+ options[:headers] ||= {}
82
+ options[:headers]['Cookie'] = cookie_header
83
+ resp = super path, options, &block
84
+ raise "post request failed with #{resp.code}: #{path}" unless resp.code == 200
85
+ resp.body
86
+ end
87
+
88
+ def self.machine_url(machine_id)
89
+ BASE_URI + "private/machines/#{machine_id}/edit"
90
+ end
91
+
92
+ end
93
+
94
+ end
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env ruby
2
+ require 'commander/import'
3
+
4
+ program :name, 'boxcutter'
5
+ program :version, Boxcutter::VERSION
6
+ program :description, 'CLI to boxpanel'
7
+
8
+ global_option('-y', '--yes', 'assume yes to all prompts') { Boxcutter.yes! }
9
+ global_option('-c', '--no-colors', 'no colors in stdout') { Boxcutter.no_colors! }
10
+
11
+ command 'machine-search'.to_sym do |c|
12
+ c.syntax ='boxcutter machine-search HOSTNAME_REGEX'
13
+ c.description = 'search for hostnames matching a regex'
14
+ c.action do |args, options|
15
+ Boxcutter.error! "missing regex: #{c.syntax}" unless args.length == 1
16
+ puts Boxcutter::table Boxcutter::Machine.search args.first
17
+ end
18
+ end
19
+ alias_command :'msearch', :'machine-search'
20
+
21
+ command 'machine-show'.to_sym do |c|
22
+ c.syntax ='boxcutter machine-show HOSTNAME'
23
+ c.description = "show a host's attributes"
24
+ c.action do |args, options|
25
+ Boxcutter.error! "missing hostname: #{c.syntax}" unless args.length == 1
26
+ puts Boxcutter::table Boxcutter::Machine.get args.first
27
+ end
28
+ end
29
+ alias_command :'mshow', :'machine-show'
30
+
31
+ command 'password'.to_sym do |c|
32
+ c.syntax ='boxcutter password NAME_REGEX'
33
+ c.description = 'search for passowords matching a given name regexp'
34
+ c.action do |args, options|
35
+ Boxcutter.error! "missing password regexp: #{c.syntax}" unless args.length == 1
36
+ puts Boxcutter::table Boxcutter::Password.search args.first
37
+ end
38
+ end
39
+ alias_command :'pw', :'password'
40
+
41
+ command 'kvm'.to_sym do |c|
42
+ c.syntax = 'boxcutter kvm PASSWORD_NAME_REGEX'
43
+ c.description = 'start a kvm console by password name'
44
+ c.action do |args, options|
45
+ Boxcutter.error! "missing kvm name: #{c.syntax}" unless args.length == 1
46
+ creds = Boxcutter::Password.search args.first
47
+ puts Boxcutter::table creds
48
+ error! "password regex is not unique: #{args.first}" if creds.length > 2
49
+ Boxcutter::KVM.start creds.last[1], creds.last[2], creds.last[3]
50
+ end
51
+ end
52
+
53
+ command 'reboot'.to_sym do |c|
54
+ c.syntax = 'boxcutter reboot HOSTNAME'
55
+ c.description = 'reboot a host'
56
+ c.action do |args, options|
57
+ Boxcutter.error! "missing hostname: #{c.syntax}" unless args.length == 1
58
+ machine = Boxcutter::Machine.get args.first
59
+ Boxcutter::Machine::machine_summary(machine).each { |msg| Boxcutter.msg msg }
60
+ Boxcutter.confirm "this will reboot #{machine[:hostname]}!" do
61
+ Boxcutter::Machine.power_cycle! machine
62
+ Boxcutter::msg "reboot task issued for #{machine[:hostname]}"
63
+ end
64
+ end
65
+ end
66
+
67
+ command 'razor'.to_sym do |c|
68
+ c.syntax = 'boxcutter razor HOSTNAME RAZOR_TAG_NAME'
69
+ c.description = 'reimage a host with razor'
70
+ c.action do |args, options|
71
+ machine = Boxcutter::Machine.get(args.first)
72
+ Boxcutter.error! "must provide hostname and razor tag name" unless args.length == 2
73
+ Boxcutter::Machine::machine_summary(machine).each { |msg| Boxcutter.msg msg }
74
+ Boxcutter.confirm "this will re-image #{args.first}!" do
75
+ Boxcutter::Razor.image! machine, args.last
76
+ end
77
+ end
78
+ end
79
+
80
+ command 'razor-tags'.to_sym do |c|
81
+ c.syntax = 'boxcutter razor-tags'
82
+ c.description = 'show available razor tags'
83
+ c.action do
84
+ tags = Boxcutter::Razor::StubbleClient.tags
85
+ puts Boxcutter::table([[:name]] + tags.map { |t| [ t ] })
86
+ end
87
+ end
88
+
89
+ command 'razor-log'.to_sym do |c|
90
+ c.syntax = 'boxcutter razor-log HOSTNAME'
91
+ c.description = 'show current razor log for a host'
92
+ c.action do |args, options|
93
+ Boxcutter::error! "must provide hostname" unless args.length == 1
94
+ logs = Boxcutter::Razor::StubbleClient.log(Boxcutter::Machine::eth0_mac(Boxcutter::Machine.get(args.first)))
95
+ if logs.any?
96
+ logs.each { |l| l['timestamp'] = DateTime.strptime(l['timestamp'].to_s,'%s').to_s if l['timestamp'] }
97
+ logs.each { |l| l.delete 'node_uuid' if l['node_uuid'] }
98
+ puts Boxcutter::table_of_hashes(logs)
99
+ else
100
+ Boxcutter::error! "no logs for #{args.first}"
101
+ end
102
+ end
103
+ end
104
+
105
+ command 'update'.to_sym do |c|
106
+ c.syntax = 'boxcutter update'
107
+ c.description = 'update to the latest version'
108
+ c.action do |args, options|
109
+ Boxcutter::update
110
+ end
111
+ end
@@ -0,0 +1,32 @@
1
+ module Boxcutter
2
+ module Git
3
+
4
+ REPO = "git@github.blueboxgrid.com:bluebox-tech/boxcutter.git"
5
+ LOCAL_CLONE = "/tmp/boxcutter_gem"
6
+
7
+ def self.upstream_version
8
+ File.read("#{LOCAL_CLONE}/lib/bluebox-boxcutter/version.rb").match(/VERSION\s?=\s?['"](.*)['"]/)[1]
9
+ end
10
+
11
+ def self.clone
12
+ if git?
13
+ `git clone #{REPO} #{LOCAL_CLONE} 2>&1 > /dev/null`
14
+ if $?.to_i != 0
15
+ Boxcutter.error! "Problem cloning git repo: #{REPO} to #{LOCAL_CLONE}"
16
+ end
17
+ else
18
+ Boxcutter.error! "Could not find git"
19
+ end
20
+ end
21
+
22
+ def self.rm_clone
23
+ FileUtils.rm_rf LOCAL_CLONE
24
+ end
25
+
26
+ def self.git?
27
+ `git --version`
28
+ $?.to_i == 0
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,36 @@
1
+ require 'tempfile'
2
+ require 'uri'
3
+
4
+ module Boxcutter
5
+ module KVM
6
+
7
+ # given a kvm url and creds, start the java kvm console thingy.
8
+ def self.start(url, user, password)
9
+ u = URI url
10
+ base, path = [ "#{u.scheme}://#{u.host}", u.path ]
11
+ path = '/auth.asp' # path is missing from some of the password entries.
12
+
13
+ client = Class.new do
14
+ include HTTParty
15
+ base_uri base
16
+ end
17
+
18
+ response = client.post(path, :body => {:login => user, :password => password, :action_login => 'Login'})
19
+ cookie = response.request.options[:headers]['Cookie']
20
+ jnlp_contents = client.get(extract_jnlp_path(response.body), :headers => {'Cookie' => cookie}).body
21
+ run_jnlp(jnlp_contents)
22
+ end
23
+
24
+ def self.extract_jnlp_path(html)
25
+ Nokogiri::HTML(html).css('a').map { |a| a['href'] }.select { |a| a =~ /spider.jnlp/ }.first
26
+ end
27
+
28
+ def self.run_jnlp(contents)
29
+ Tempfile.new('kvm-webstart').tap do |f|
30
+ f.write contents
31
+ f.close
32
+ `javaws #{f.path} &`
33
+ end
34
+ end
35
+ end
36
+ end