#!/usr/bin/env ruby ################################################################################ # # Author: Zachary Patten # Copyright: Copyright (c) Zachary Patten # License: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ################################################################################ require 'gli' require 'testlab' include GLI::App include TestLab::Utility::Misc version TestLab::VERSION program_desc %(A framework for building lightweight virtual infrastructure using LXC) # program_long_desc %(Program Long Description) sort_help :manually default_command :help # LAB CREATE ############# desc 'Create the test lab' command :create do |create| create.action do |global_options,options,args| @testlab.create end end # LAB DESTROY ############## desc 'Destroy the test lab' command :destroy do |destroy| destroy.action do |global_options,options,args| @testlab.destroy end end # LAB ONLINE ############# desc 'Online the test lab' command :up do |up| up.action do |global_options,options,args| @testlab.up end end # LAB OFFLINE ############## desc 'Offline the test lab' command :down do |down| down.action do |global_options,options,args| @testlab.down end end # LAB SETUP ############ desc 'Setup the test lab infrastructure' command :setup do |setup| setup.action do |global_options,options,args| @testlab.setup end end # LAB TEARDOWN ############### desc 'Teardown the test lab infrastructure' command :teardown do |teardown| teardown.action do |global_options,options,args| @testlab.teardown end end # LAB STATUS ############# desc 'Display information on the status of the test lab' command :status do |status| status.action do |global_options,options,args| @testlab.ui.stdout.puts("\nNODES:".green.bold) commands[:node].commands[:status].execute({}, {}, []) @testlab.ui.stdout.puts("\nNETWORKS:".green.bold) commands[:network].commands[:status].execute({}, {}, []) @testlab.ui.stdout.puts("\nCONTAINERS:".green.bold) commands[:container].commands[:status].execute({}, {}, []) end end # NODES ######## desc 'Manage nodes' arg_name 'Describe arguments to node here' command :node do |c| c.desc 'Node ID or Name' c.arg_name 'node' c.flag [:i, :id] # NODE SSH ########### c.desc 'Open an SSH console to a node' c.command :ssh do |ssh| ssh.action do |global_options,options,args| help_now!('id is required') if options[:id].nil? node = @testlab.nodes.select{ |n| n.id.to_sym == options[:id].to_sym }.first node.nil? and raise TestLab::TestLabError, "We could not find the node you supplied!" node.ssh.console end end # NODE STATUS ############## c.desc 'Display the status of node(s)' c.long_desc 'Displays the status of all nodes or a single node if supplied via the ID parameter.' c.command :status do |status| status.action do |global_options, options, args| if options[:id].nil? # No ID supplied; show everything ZTK::Report.new(:ui => @testlab.ui).spreadsheet(@testlab.nodes, TestLab::Node::STATUS_KEYS) do |node| OpenStruct.new(node.status) end else # ID supplied; show just that item node = @testlab.nodes.select{ |c| c.id.to_sym == options[:id].to_sym }.first node.nil? and raise TestLab::TestLabError, "We could not find the node you supplied!" ZTK::Report.new(:ui => @testlab.ui).list(node, TestLab::Node::STATUS_KEYS) do |node| OpenStruct.new(node.status) end end end end end # NETWORKS ########### desc 'Manage networks' arg_name 'Describe arguments to network here' command :network do |c| c.desc 'Network ID or Name' c.arg_name 'network' c.flag [:i, :id] # ROUTES ######### c.desc 'Manage routes' c.command :route do |route| # ROUTE ADD ############ route.desc 'Add routes to lab networks' route.command :add do |add| add.action do |global_options,options,args| help_now!('id is required') if options[:id].nil? network = @testlab.networks.select{ |c| c.id.to_sym == options[:id].to_sym }.first network.nil? and raise TestLab::TestLabError, "We could not find the network you supplied!" network.manage_route(:add) @testlab.ui.stdout.puts("Added routes successfully!".green.bold) @testlab.ui.stdout.puts %x(netstat -nr | grep '#{network.node.ip}').strip end end # ROUTE DEL ############ route.desc 'Delete routes to lab networks' route.command :del do |del| del.action do |global_options,options,args| help_now!('id is required') if options[:id].nil? network = @testlab.networks.select{ |c| c.id.to_sym == options[:id].to_sym }.first network.nil? and raise TestLab::TestLabError, "We could not find the network you supplied!" network.manage_route(:del) @testlab.ui.stdout.puts("Deleted routes successfully!".red.bold) @testlab.ui.stdout.puts %x(netstat -nr | grep '#{network.node.ip}').strip end end # ROUTE SHOW ############# route.desc 'Show routes to lab networks' route.command :show do |show| show.action do |global_options,options,args| help_now!('id is required') if options[:id].nil? network = @testlab.networks.select{ |c| c.id.to_sym == options[:id].to_sym }.first network.nil? and raise TestLab::TestLabError, "We could not find the network you supplied!" @testlab.ui.stdout.puts("TestLab routes:".green.bold) case RUBY_PLATFORM when /darwin/ then @testlab.ui.stdout.puts %x(netstat -nrf inet | grep '#{network.node.ip}').strip when /linux/ then @testlab.ui.stdout.puts %x(netstat -nr | grep '#{network.node.ip}').strip end end end end # NETWORK STATUS ################# c.desc 'Display the status of network(s)' c.long_desc 'Displays the status of all networks or a single network if supplied via the ID parameter.' c.command :status do |status| status.action do |global_options, options, args| if options[:id].nil? # No ID supplied; show everything networks = @testlab.networks.delete_if{|n| n.node.dead? } if networks.count == 0 @testlab.ui.stderr.puts("You either have no networks defined or dead nodes!".yellow) else ZTK::Report.new(:ui => @testlab.ui).spreadsheet(networks, TestLab::Network::STATUS_KEYS) do |network| OpenStruct.new(network.status) end end else # ID supplied; show just that item network = @testlab.networks.select{ |c| c.id.to_sym == options[:id].to_sym }.first network.nil? and raise TestLab::TestLabError, "We could not find the network you supplied!" ZTK::Report.new(:ui => @testlab.ui).list(network, TestLab::Network::STATUS_KEYS) do |network| OpenStruct.new(network.status) end end end end end # CONTAINERS ############# desc 'Manage containers' arg_name 'Describe arguments to container here' command :container do |c| c.desc 'Container ID or Name' c.arg_name 'container' c.flag [:i, :id] # CONTAINER SSH ################ c.desc 'Open an SSH console to a container' c.command :ssh do |ssh| ssh.desc 'Username' ssh.arg_name 'username' ssh.flag [:u, :user] ssh.action do |global_options, options, args| help_now!('id is required') if options[:id].nil? container = @testlab.containers.select{ |n| n.id.to_sym == options[:id].to_sym }.first container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!" ssh_options = Hash.new ssh_options[:user] = options[:user] container.ssh(ssh_options).console end end # CONTAINER STATUS ################### c.desc 'Display the status of container(s)' c.long_desc <<-EOF Displays the status of all containers or a single container if supplied via the ID parameter. EOF c.command :status do |status| status.action do |global_options, options, args| if options[:id].nil? # No ID supplied; show everything containers = @testlab.containers.delete_if{ |c| c.node.dead? } if containers.count == 0 @testlab.ui.stderr.puts("You either have no containers defined or dead nodes!".yellow) else ZTK::Report.new(:ui => @testlab.ui).spreadsheet(containers, TestLab::Container::STATUS_KEYS.reject{|k| k == :fqdn}) do |container| OpenStruct.new(container.status.reject{|k,v| k == :fqdn}) end end else # ID supplied; show just that item container = @testlab.containers.select{ |c| c.id.to_sym == options[:id].to_sym }.first container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!" ZTK::Report.new(:ui => @testlab.ui).list(container, TestLab::Container::STATUS_KEYS) do |container| OpenStruct.new(container.status) end end end end # CONTAINER RECYCLE #################### c.desc 'Recycles a container' c.long_desc <<-EOF Recycles a container. The container is taken through a series of state changes to ensure it is pristine. The containers is cycled in this order: Down -> Destroy -> Create -> Up EOF c.command :recycle do |recycle| recycle.action do |global_options, options, args| if options[:id].nil? help_now!('id is required') if options[:id].nil? else container = @testlab.containers.select{ |c| c.id.to_sym == options[:id].to_sym }.first container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!" container.teardown container.setup end end end # CONTAINER SETUP #################### c.desc 'Setup a container' c.long_desc <<-EOF Setup a container. The container is created, started and provisioned. EOF c.command :setup do |setup| setup.action do |global_options, options, args| if options[:id].nil? help_now!('id is required') if options[:id].nil? else container = @testlab.containers.select{ |c| c.id.to_sym == options[:id].to_sym }.first container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!" container.setup end end end # CONTAINER TEARDOWN #################### c.desc 'Teardown a container' c.long_desc <<-EOF Teardown a container. The container is offlined and destroyed. EOF c.command :teardown do |teardown| teardown.action do |global_options, options, args| if options[:id].nil? help_now!('id is required') if options[:id].nil? else container = @testlab.containers.select{ |c| c.id.to_sym == options[:id].to_sym }.first container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!" container.teardown end end end # CONTAINER CLONE ################## c.desc 'Clone a container' c.long_desc <<-EOF Clone a container. The container is offlined and an ephemeral copy of it is started. EOF c.command :clone do |clone| clone.action do |global_options, options, args| if options[:id].nil? help_now!('id is required') if options[:id].nil? else container = @testlab.containers.select{ |c| c.id.to_sym == options[:id].to_sym }.first container.nil? and raise TestLab::TestLabError, "We could not find the container you supplied!" container.clone end end end end pre do |global,command,options,args| # Pre logic here # Return true to proceed; false to abort and not call the # chosen command # Use skips_pre before a command to skip this block # on that command only log_file = File.join(Dir.pwd, "testlab.log") @logger = ZTK::Logger.new(log_file) @ui = ZTK::UI.new(:logger => @logger) @testlab = TestLab.new(:ui => @ui) message = format_message("TestLab v#{TestLab::VERSION} Loaded".black.bold) @testlab.ui.stdout.puts(message) true end post do |global,command,options,args| # Post logic here # Use skips_post before a command to skip this # block on that command only end on_error do |exception| @ui.stderr.puts @ui.stderr.puts(format_message(["ERROR:".red, exception.message.red.bold].join(' '))) case exception when GLI::BadCommandLine, GLI::UnknownCommand, GLI::UnknownCommandArgument, GLI::UnknownGlobalArgument then command_regex = /Command '([\w]+)' / command = exception.message.scan(command_regex).flatten.first @ui.stderr.puts commands[:help] and commands[:help].execute({}, {}, (command.nil? ? [] : [command.to_s])) false else @logger.fatal { exception.inspect } exception.backtrace.each do |line| @logger.logdev.write("#{line}\n") end false end end exit run(ARGV)