virtualmaster 0.0.3

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.
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ vendor/bundle
6
+ vendor/cache
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in vmaster.gemspec
4
+ gemspec
data/README.mdown ADDED
@@ -0,0 +1,55 @@
1
+ # VirtualMaster command line interface
2
+
3
+ Proof of concept of VirtualMaster command line interface
4
+
5
+ # virtualmaster config
6
+ Running virtualmaster for first time.
7
+ Setting up environment.
8
+
9
+ Your API credentials are available from http://www.virtualmaster.cz/en/api#settings
10
+
11
+ Enter your API username:
12
+ Enter your API password:
13
+
14
+ Settings stored within ~/.virtualmaster
15
+
16
+ # virtualmaster create demo1 --image lucid
17
+ Using image 'lucid' with id (999).
18
+ Creating 'micro' instance (512 MB memory/5 GB storage).
19
+ Instance launch request accepted (instance id 44444).
20
+
21
+ Default password 'AdVc:PBi8&7L'.
22
+
23
+ # virtualmaster instances
24
+ +-------+---------+----------------+
25
+ | name | state | ip_address |
26
+ +-------+---------+----------------+
27
+ | demo1 | RUNNING | 195.140.253.88 |
28
+ +-------+---------+----------------+
29
+
30
+ ## Install
31
+
32
+ The current version of `virtualmaster` command line interface should work with Ruby 1.8.7+.
33
+
34
+ ## Automatically install SSH keys
35
+
36
+ VirtualMaster CLI can install your SSH keys to a remote machine automatically using `--copy-id` switch.
37
+
38
+
39
+ virtualmaster create demo1 --image ubuntu_lucid --copy-id
40
+ Using image 'ubuntu_lucid' with ID 124
41
+ Creating 'micro' instance (512 MB memory/10 GB storage)
42
+ Instance launch request accepted. Instance ID 45387
43
+
44
+ Default password 'vBK7i!kK'
45
+ Waiting for instance............
46
+ Loading identity file
47
+
48
+ Instance ready!
49
+ Try to login using `ssh root@195.140.253.130'
50
+
51
+ If you want to specify other key (ie. not ~/.ssh/id_rsa) use option `--identity IDENTITY_FILE`.
52
+
53
+ ## More information
54
+
55
+ Additional topics are available in [the wiki](https://github.com/Virtualmaster/virtualmaster-cli/wiki).
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/bin/virtualmaster ADDED
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ begin
4
+ require 'vmaster'
5
+ rescue LoadError
6
+ vmaster_path = File.expand_path('../../lib', __FILE__)
7
+
8
+ $:.unshift(vmaster_path)
9
+
10
+ require 'vmaster'
11
+ end
12
+
13
+ program :name, "virtualmaster"
14
+ program :version, VirtualMaster::VERSION
15
+ program :description, "VirtualMaster command line interface"
16
+ program :help_formatter, :compact
17
+
18
+ default_command :help
19
+
20
+ VirtualMaster::CLI.run do
21
+ end
22
+
data/lib/vmaster.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "vmaster/version"
2
+ require "vmaster/helpers"
3
+ require "vmaster/cli"
4
+ require "commander/import"
5
+
6
+ # include commands
7
+ require 'vmaster/config_command'
8
+ require 'vmaster/server_commands'
9
+
@@ -0,0 +1,32 @@
1
+ require 'yaml'
2
+ require 'deltacloud'
3
+
4
+ module VirtualMaster
5
+ class CLI
6
+ @@api = nil
7
+ @@config = nil
8
+
9
+ def self.run
10
+ # load config
11
+ config_file = File.join(ENV["HOME"], ".virtualmaster")
12
+ if File.exists? config_file
13
+ config = YAML::load(File.open(config_file))
14
+
15
+ @@config = config.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
16
+
17
+ @@api = DeltaCloud.new(@@config[:username], @@config[:password], VirtualMaster::DEFAULT_URL)
18
+ end
19
+
20
+ yield
21
+ end
22
+
23
+ def self.api
24
+ abort "No configuration available! Please run 'virtualmaster config' first!" unless @@api
25
+ @@api
26
+ end
27
+
28
+ def self.config
29
+ @@config
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ require 'yaml'
2
+
3
+
4
+ command :config do |c|
5
+ c.description = "Configure VirtualMaster API username and password"
6
+
7
+ c.option '--username NAME', String, 'API username'
8
+ c.option '--password PASSWD', String, 'API password'
9
+
10
+ c.action do |args, options|
11
+ config_file = File.join(ENV['HOME'], VirtualMaster::CONFIG_FILE)
12
+ if File.exists?(config_file)
13
+ abort "Default configuration file already exists: #{config_file}"
14
+ else
15
+ say "Running virtualmaster for first time"
16
+ end
17
+
18
+ unless options.username && options.password
19
+ say "\n"
20
+ say "Your API credentials are available from http://www.virtualmaster.cz/en/api#settings"
21
+ say "\n"
22
+ end
23
+
24
+ options.username = ask("Enter API username:") unless options.username
25
+ options.password = password("Enter API password:", "*") unless options.password
26
+
27
+ # verify and store credentials
28
+ begin
29
+ api = DeltaCloud.new(options.username, options.password, VirtualMaster::DEFAULT_URL)
30
+
31
+ config = {
32
+ 'username' => options.username,
33
+ 'password' => options.password,
34
+ 'default_image' => VirtualMaster::DEFAULT_IMAGE,
35
+ 'images' => VirtualMaster::IMAGES
36
+ }
37
+
38
+ File.open(config_file, 'w') do |f|
39
+ f.puts YAML.dump(config)
40
+ end
41
+
42
+ say "Setting stored under #{config_file}"
43
+ rescue DeltaCloud::API::BackendError => e
44
+ say "Unable to connect to VirtualMaster API: #{e.message}"
45
+ rescue Exception => e
46
+ say "Unable to configure environment: #{e.message}"
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,71 @@
1
+ module VirtualMaster
2
+
3
+ CONFIG_FILE = ".virtualmaster"
4
+ DEFAULT_URL = "https://www.virtualmaster.cz/services/deltacloud"
5
+
6
+ DEFAULT_IMAGE = 124
7
+ DEFAULT_PROFILE = "micro"
8
+
9
+ # pre-defined list of images
10
+ IMAGES = {
11
+ :ubuntu_lucid => 124,
12
+ :debian_squeeze => 1741,
13
+ :centos_6 => 1743
14
+ }
15
+
16
+ PROFILES = {
17
+ :nano => {
18
+ :memory => 256,
19
+ :storage => 3840
20
+ },
21
+ :micro => {
22
+ :memory => 512,
23
+ :storage => 6144
24
+ },
25
+ :milli => {
26
+ :memory => 1024,
27
+ :storage => 20480,
28
+ },
29
+ :small => {
30
+ :memory => 2048,
31
+ :storage => 30720
32
+ },
33
+ :medium => {
34
+ :memory => 4096,
35
+ :storage => 40960
36
+ }
37
+ #:large => {
38
+ # :memory => 8192,
39
+ # :storage => 8192
40
+ #}
41
+ }
42
+
43
+
44
+ module Helpers
45
+ def self.get_instances
46
+ VirtualMaster::CLI.api.instances
47
+ end
48
+
49
+ def self.get_instance(name)
50
+ get_instances.each do |instance|
51
+ return instance if instance.name == name
52
+ end
53
+
54
+ nil
55
+ end
56
+
57
+ def self.get_hw_profile(memory, storage)
58
+ api = VirtualMaster::CLI.api
59
+
60
+ profile_list = api.hardware_profiles.reject { |p| p.memory.value.to_i != memory && p.storage.value.to_i != storage }
61
+
62
+ profile_list.first
63
+ end
64
+
65
+ def self.create_instance(name, image_id, profile_id)
66
+ api = VirtualMaster::CLI.api
67
+
68
+ api.create_instance(image_id, :name => name, :hwp_id => profile_id)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,163 @@
1
+ require 'terminal-table'
2
+ require 'net/ssh'
3
+ require 'logger'
4
+ require "base64"
5
+ require 'openssl'
6
+
7
+ log = Logger.new(STDOUT)
8
+ log.level = Logger::WARN
9
+
10
+ # SSH copy id
11
+ # FIXME how to select the right key
12
+
13
+ command :create do |c|
14
+ c.description = "Launch new server instance"
15
+ c.option '--image TEMPLATE', String, 'instance template to use'
16
+ c.option '--profile PROFILE', String, 'instance hardware profile'
17
+ c.option '--copy-id', 'install public key on a machine'
18
+ c.option '--identity IDENTITY', String, 'SSH identity to use (with --copy-id)'
19
+ c.option '--wait', 'wait for instance to become operational'
20
+ c.action do |args, options|
21
+ # default values
22
+ options.default :identity => File.join(ENV['HOME'], '.ssh/id_rsa')
23
+
24
+ name = args.shift || abort('Server name required')
25
+
26
+ # verify server name
27
+ abort("Virtual server with name #{name} already exists!") if VirtualMaster::Helpers.get_instance(name)
28
+
29
+ # image
30
+ image_name = nil
31
+ image_id = VirtualMaster::CLI.config[:default_image] || VirtualHost::DEFAULT_IMAGE
32
+
33
+ if options.image
34
+ image_name = options.image
35
+
36
+ if image_name.match /^id:/
37
+ # use image_id directly
38
+ image_id = image_name[3..-1].to_i
39
+
40
+ image_name = nil
41
+ else
42
+ # lookup predefined images
43
+ image_id = VirtualMaster::CLI.config[:images][image_name.to_sym]
44
+
45
+ abort "Image '#{image_name}' not recognized!" unless image_id
46
+ end
47
+ end
48
+
49
+ say image_name ? "Using image '#{image_name}' with ID #{image_id}" : "Using image with ID #{image_id}"
50
+
51
+ # instance hardware profile
52
+ profile_name = options.profile || VirtualMaster::DEFAULT_PROFILE
53
+
54
+ profile = VirtualMaster::PROFILES[profile_name.to_sym]
55
+ abort "Image name '#{options.profile}' not recognized!" unless profile
56
+
57
+ hwp = VirtualMaster::Helpers.get_hw_profile(profile[:memory], profile[:storage])
58
+ abort "Internal error: hardware profile not available" unless hwp
59
+
60
+ say "Creating '#{profile_name}' instance (#{profile[:memory]} MB memory/#{profile[:storage]/1024} GB storage)"
61
+
62
+ instance = VirtualMaster::Helpers.create_instance(name, image_id, hwp.id)
63
+
64
+ # TODO handle exceptions (invalid image/profile, limits, etc.)
65
+
66
+ say "Instance launch request accepted. Instance ID #{instance.id}"
67
+
68
+ # FIXME authentication is missrepresented within Ruby object
69
+ password = instance.authentication[:username]
70
+ say "\n"
71
+ say "Default password '#{password}'"
72
+
73
+ # copy-id implies waiting for instance to become operational
74
+ if options.wait || options.copy_id
75
+ print 'Waiting for instance'
76
+
77
+ while (instance = VirtualMaster::Helpers.get_instance(name)).state != "RUNNING" do
78
+ print '.'
79
+
80
+ sleep(5)
81
+ end
82
+
83
+ puts
84
+
85
+ # copy ssh id
86
+ if options.copy_id
87
+ authorized_key = nil
88
+
89
+ abort "Specified identity file #{options.identity} doesn't exists!" unless File.exist?(options.identity)
90
+
91
+ say "Loading identity file\n"
92
+ key = OpenSSL::PKey::RSA.new File.read options.identity
93
+
94
+ # build authorized key output string
95
+ authtype = key.class.to_s.split('::').last.downcase
96
+ b64pub = ::Base64.encode64(key.to_blob).strip.gsub(/[\r\n]/, '')
97
+ authorized_key = "ssh-%s %s\n" % [authtype, b64pub] # => ssh-rsa AAAAB3NzaC1...=
98
+
99
+ Net::SSH.start(instance.public_addresses.first[:address], 'root', :password => password) do |ssh|
100
+ # TODO exception handling
101
+ output = ssh.exec!("mkdir ~/.ssh")
102
+ output = ssh.exec!("echo '#{authorized_key}' >>~/.ssh/authorized_keys")
103
+ end
104
+ end
105
+
106
+ puts
107
+ puts "Instance ready!"
108
+ puts "Try to login using `ssh root@#{instance.public_addresses.first[:address]}'"
109
+ end
110
+ end
111
+ end
112
+
113
+ command :list do |c|
114
+ c.description = "List all running servers"
115
+ c.action do |args, options|
116
+ instances = []
117
+
118
+ VirtualMaster::Helpers.get_instances.each do |instance|
119
+ unless instance.public_addresses.first.nil?
120
+ ip_address = instance.public_addresses.first[:address]
121
+ else
122
+ ip_address = "(not assigned)"
123
+ end
124
+
125
+ instances << [instance.name, instance.state, ip_address]
126
+ end
127
+
128
+ abort "No instances found" if instances.empty?
129
+
130
+ table = Terminal::Table.new :headings => ['name','state','ip_address'], :rows => instances
131
+ puts table
132
+ end
133
+ end
134
+
135
+ def instance_action(action, args)
136
+ name = args.shift || abort('server name required')
137
+
138
+ instance = VirtualMaster::Helpers.get_instance(name)
139
+ instance.send("#{action}!")
140
+ end
141
+
142
+ %w{start reboot stop shutdown destroy}.each do |cmd|
143
+ command cmd do |c|
144
+ c.syntax = "virtualmaster #{c.name} SERVER"
145
+
146
+ case c.name
147
+ when "start"
148
+ c.description = "Start server (when stopped)"
149
+ when "reboot"
150
+ c.description = "Reboot server"
151
+ when "stop"
152
+ c.description = "Stop server"
153
+ when "shutdown"
154
+ c.description = "Shutdown server (ACPI)"
155
+ when "destroy"
156
+ c.description = "Remove server"
157
+ end
158
+
159
+ c.action do |args, options|
160
+ instance_action(c.name, args)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,3 @@
1
+ module VirtualMaster
2
+ VERSION = "0.0.3"
3
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+
3
+ describe "VirtualMaster commands" do
4
+ before :each do
5
+ @runner = Commander::Runner.instance
6
+ end
7
+
8
+ it "should have command :config" do
9
+ @runner.commands.should have_key('config')
10
+ end
11
+
12
+ it "should have command server commands" do
13
+ %w{create list start reboot stop shutdown destroy}.each do |cmd|
14
+ @runner.commands.should have_key(cmd)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ $:.unshift File.dirname(__FILE__) + '/../lib'
2
+
3
+ require 'vmaster'
4
+
5
+ program :name, "virtualmaster"
6
+ program :version, VirtualMaster::VERSION
7
+ program :description, "VirtualMaster command line interface"
8
+ program :help_formatter, :compact
9
+
10
+ default_command :test
11
+
12
+ command :test do |c|
13
+ end
@@ -0,0 +1,31 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "vmaster/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "virtualmaster"
7
+ s.version = VirtualMaster::VERSION
8
+ s.authors = ["Radim Marek"]
9
+ s.email = ["radim@laststation.net"]
10
+ s.homepage = "https://github.com/virtualmaster/virtualmaster-cli"
11
+ s.summary = %q{Command line interface to VirtualMaster}
12
+ s.description = %q{Command line interface to VirtualMaster. Control your virtual infrastructure.}
13
+
14
+ s.rubyforge_project = "vmaster"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ # s.add_development_dependency "rspec"
23
+ # s.add_runtime_dependency "rest-client"
24
+
25
+ s.add_dependency "commander", "~> 4.1.2"
26
+ s.add_dependency "deltacloud-client", "~> 0.5.0"
27
+ s.add_dependency "terminal-table", "~> 1.4.4"
28
+ s.add_dependency "net-ssh", "~> 2.3.0"
29
+
30
+ s.add_development_dependency "rspec", "~> 2"
31
+ end
metadata ADDED
@@ -0,0 +1,117 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: virtualmaster
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Radim Marek
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: commander
16
+ requirement: &70220237000360 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 4.1.2
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70220237000360
25
+ - !ruby/object:Gem::Dependency
26
+ name: deltacloud-client
27
+ requirement: &70220236995660 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: 0.5.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70220236995660
36
+ - !ruby/object:Gem::Dependency
37
+ name: terminal-table
38
+ requirement: &70220236993960 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 1.4.4
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70220236993960
47
+ - !ruby/object:Gem::Dependency
48
+ name: net-ssh
49
+ requirement: &70220236992760 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 2.3.0
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70220236992760
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &70220236991620 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '2'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70220236991620
69
+ description: Command line interface to VirtualMaster. Control your virtual infrastructure.
70
+ email:
71
+ - radim@laststation.net
72
+ executables:
73
+ - virtualmaster
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - README.mdown
80
+ - Rakefile
81
+ - bin/virtualmaster
82
+ - lib/vmaster.rb
83
+ - lib/vmaster/cli.rb
84
+ - lib/vmaster/config_command.rb
85
+ - lib/vmaster/helpers.rb
86
+ - lib/vmaster/server_commands.rb
87
+ - lib/vmaster/version.rb
88
+ - spec/commands_spec.rb
89
+ - spec/spec_helper.rb
90
+ - virtualmaster.gemspec
91
+ homepage: https://github.com/virtualmaster/virtualmaster-cli
92
+ licenses: []
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ! '>='
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project: vmaster
111
+ rubygems_version: 1.8.11
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Command line interface to VirtualMaster
115
+ test_files:
116
+ - spec/commands_spec.rb
117
+ - spec/spec_helper.rb