bluebox-boxcutter 0.0.14

Sign up to get free protection for your applications and to get access to all the features.
@@ -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