#!/usr/bin/env ruby require 'thor' require 'cucumber-chef' # $logger = Cucumber::Chef.logger class CucumberChef < Thor include Thor::Actions no_tasks do def initalize_config source_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cucumber", "chef", "templates", "cucumber-chef")) destination_dir = File.expand_path(File.join(Cucumber::Chef.locate_parent(".chef"), ".cucumber-chef")) FileUtils.mkdir_p(destination_dir) CucumberChef.source_root(source_dir) templates = { "config-rb.erb" => "config.rb" } templates.each do |source, destination| template(source, File.join(destination_dir, destination)) end puts say "Ucanhaz Cucumber-Chef now! Rock on.", :green end def create_project(project) @project = project source_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", "lib", "cucumber", "chef", "templates", "cucumber")) destination_dir = Cucumber::Chef.locate_parent(".chef") CucumberChef.source_root source_dir templates = { "readme.erb" => "features/#{project}/README.md", "example_feature.erb" => "features/#{project}/#{project}.feature", "example_steps.erb" => "features/#{project}/step_definitions/#{project}_steps.rb", "example_labfile.erb" => "Labfile", "env.rb" => "features/support/env.rb", "cc-hooks.rb" => "features/support/cc-hooks.rb", "readme-data_bags.erb" => "features/support/data_bags/README.md", "readme-roles.erb" => "features/support/roles/README.md", "readme-keys.erb" => "features/support/keys/README.md", "readme-environments.erb" => "features/support/environments/README.md" } templates.each do |source, destination| template(source, File.join(destination_dir, destination)) end end def boot tag = Cucumber::Chef.tag("cucumber-chef") puts(tag) Cucumber::Chef.boot(tag) $logger = Cucumber::Chef.logger @is_rc = Cucumber::Chef.is_rc? @options.test? and Cucumber::Chef::Config.test end def fatal(message) puts(set_color(message, :red, :bold)) exit(255) end end ################################################################################ desc "init", "Initalize cucumber-chef configuration" def init initalize_config end ################################################################################ # SETUP ################################################################################ desc "setup", "Setup the cucumber-chef test lab" method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY" def setup boot if (test_lab = Cucumber::Chef::TestLab.new) if (provider = test_lab.create) if (provisioner = Cucumber::Chef::Provisioner.new(test_lab)) provisioner.build puts puts("If you are using AWS, be sure to log into the chef-server webui and change the default admin password at least.") puts puts("Your test lab has now been provisioned! Enjoy!") puts test_lab.status else puts(set_color("Could not create the provisioner!", :red, true)) end else puts(set_color("Could not create the server!", :red, true)) end else puts(set_color("Could not create a new instance of test lab!", :red, true)) end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e.message) end ################################################################################ # DESTROY ################################################################################ desc "destroy [container] [...]", "Destroy the cucumber-chef test lab or a single or multiple containers if specified" method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY" def destroy(*args) boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.exists? if args.count == 0 test_lab.status if yes?(set_color("Are you sure you want to destroy the test lab?", :red, true)) puts puts(set_color("You have 5 seconds to abort!", :red, true)) puts 5.downto(1) do |x| print("#{x}...") sleep(1) end puts("BOOM!") puts ZTK::Benchmark.bench(:message => "Destroy #{Cucumber::Chef::Config.provider.upcase} instance '#{test_lab.id}'", :mark => "completed in %0.4f seconds.") do test_lab.destroy end else puts puts(set_color("Whew! That was close!", :green, true)) end else if yes?(set_color("Are you sure you want to destroy the container#{args.count > 1 ? 's' : nil} #{args.collect{|a| "'#{a}'"}.join(', ')}?", :red, true)) puts puts(set_color("You have 5 seconds to abort!", :red, true)) puts 5.downto(1) do |x| print("#{x}...") sleep(1) end puts("BOOM!") puts args.each do |container| ZTK::Benchmark.bench(:message => "Destroy container '#{container}'", :mark => "completed in %0.4f seconds.") do test_lab.containers.destroy(container) end end else puts puts(set_color("Whew! That was close!", :green, true)) end end else raise Cucumber::Chef::Error, "We could not find a running test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # UP ################################################################################ desc "up", "Power up the cucumber-chef test lab" def up boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.dead? ZTK::Benchmark.bench(:message => "Booting #{Cucumber::Chef::Config.provider.upcase} instance '#{test_lab.id}'", :mark => "completed in %0.4f seconds.") do test_lab.up end else raise Cucumber::Chef::Error, "We could not find a powered off test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # DOWN ################################################################################ desc "down", "Power off the cucumber-chef test lab" def down boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.alive? ZTK::Benchmark.bench(:message => "Downing #{Cucumber::Chef::Config.provider.upcase} instance '#{test_lab.id}'", :mark => "completed in %0.4f seconds.") do test_lab.down end else raise Cucumber::Chef::Error, "We could not find a running test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # RELOAD ################################################################################ desc "reload", "Reload the cucumber-chef test lab" def reload boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.alive? ZTK::Benchmark.bench(:message => "Reloading #{Cucumber::Chef::Config.provider.upcase} instance '#{test_lab.id}'", :mark => "completed in %0.4f seconds.") do test_lab.reload end else raise Cucumber::Chef::Error, "We could not find a running test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end desc "genmac", "Generate an RFC compliant private MAC address" def genmac boot puts Cucumber::Chef::Containers.generate_mac end desc "genip", "Generate an RFC compliant private IP address" def genip boot puts Cucumber::Chef::Containers.generate_ip end ################################################################################ # STATUS ################################################################################ desc "status", "Displays the current status of the test lab." method_option :containers, :type => :boolean, :desc => "Display container status.", :default => false method_option :attributes, :type => :boolean, :desc => "Display chef-client attributes for containers.", :default => false method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY" def status boot if (test_lab = Cucumber::Chef::TestLab.new) if @options.containers? if test_lab.alive? if test_lab.containers.count > 0 headers = [:name, :alive, :distro, :ip, :mac, :"chef version", :persist] results = ZTK::Report.new.spreadsheet(Cucumber::Chef::Container.all, headers) do |container| chef_version = "N/A" alive = (test_lab.bootstrap_ssh(:ignore_exit_status => true).exec(%(ping -n -c 1 -W 1 #{container.ip}), :silence => true).exit_code == 0) if alive chef_version = test_lab.proxy_ssh(container.id, :ignore_exit_status => true).exec(%(/usr/bin/env chef-client -v), :silence => true).output.chomp end OpenStruct.new( :name => container.id, :ip => container.ip, :mac => container.mac, :distro => container.distro, :alive => alive, :"chef version" => chef_version, :persist => container.persist, :chef_attributes => container.chef_client ) end if @options.attributes? results.rows.each do |result| puts puts("-" * results.width) puts("Chef-Client attributes for '#{result.name.to_s.downcase}':") puts("-" * results.width) puts(JSON.pretty_generate(result.chef_attributes)) end end else raise Cucumber::Chef::Error, "We could not find any containers!" end else raise Cucumber::Chef::Error, "We could not find a running test lab." end else test_lab.status end end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e.message) end ################################################################################ # SSH ################################################################################ desc "ssh [container]", "SSH to cucumber-chef test lab or [container] if specified" method_option :bootstrap, :type => :boolean, :desc => "Use the bootstrap settings", :default => false def ssh(*args) boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.alive? if @options.bootstrap? puts([set_color("Attempting bootstrap SSH connection to cucumber-chef '", :blue, true), set_color("test lab", :cyan, true), set_color("'...", :blue, true)].join) test_lab.bootstrap_ssh.console elsif args.size == 0 puts([set_color("Attempting SSH connection to the '", :blue, true), set_color("test lab", :cyan, true), set_color("'...", :blue, true)].join) test_lab.ssh.console elsif args.size > 0 container = args[0] puts([set_color("Attempting proxy SSH connection to the container '", :blue, true), set_color(container, :cyan, true), set_color("'...", :blue, true)].join) test_lab.proxy_ssh(container).console else raise Cucumber::Chef::Error, "You did not specify a valid combination of options." end else raise Cucumber::Chef::Error, "We could not find a running test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # PS ################################################################################ desc "ps [ps-options]", "Snapshot of the current cucumber-chef test lab container processes." def ps(*args) boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.alive? puts("-" * 80) test_lab.ssh.exec("lxc-ps --lxc -- #{args.join(" ")}") puts("-" * 80) else raise Cucumber::Chef::Error, "We could not find a running test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # LOG ################################################################################ desc "log", "Streams the cucumber-chef local and test lab logs to the terminal." def log boot if ($test_lab = Cucumber::Chef::TestLab.new) && $test_lab.exists? && $test_lab.alive? $tail_thread_remote = Thread.new do $test_lab.ssh.exec("tail -n 0 -f /home/#{$test_lab.ssh.config.user}/.cucumber-chef/cucumber-chef.log") end log_file = File.open(Cucumber::Chef.log_file, "r") log_file.seek(0, ::IO::SEEK_END) loop do if !(data = (log_file.readline rescue nil)).nil? print(data) else sleep(1) end end $tail_thread_remote.join else raise Cucumber::Chef::Error, "We could not find a running test lab." end rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # DIAGNOSE ################################################################################ desc "diagnose ", "Provide diagnostics from the chef-client on the specified container." method_option :strace, :type => :boolean, :desc => "output the chef-client 'chef-stacktrace.out'", :aliases => "-s", :default => true method_option :log, :type => :boolean, :desc => "output the chef-client 'chef.log'", :aliases => "-l", :default => true method_option :lines, :type => :numeric, :desc => "output the last N lines of the chef-client 'chef.log'", :aliases => "-n", :default => 1 def diagnose(container) boot if (test_lab = Cucumber::Chef::TestLab.new) && test_lab.alive? puts([set_color("Attempting to collect diagnostic information on cucumber-chef container '", :blue, true), set_color(container, :cyan, true), set_color("'...", :blue, true)].join) if @options.strace? puts puts("chef-stacktrace.out:") puts(set_color("============================================================================", :bold)) test_lab.proxy_ssh(container).exec("[[ -e /var/chef/cache/chef-stacktrace.out ]] && cat /var/chef/cache/chef-stacktrace.out") print("\n") end if @options.log? puts puts("chef.log:") puts(set_color("============================================================================", :bold)) test_lab.proxy_ssh(container).exec("[[ -e /var/log/chef/client.log ]] && tail -n #{@options.lines} /var/log/chef/client.log") end else raise Cucumber::Chef::Error, "We could not find a running test lab." end puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # DISPLAYCONFIG ################################################################################ desc "displayconfig", "Display the current cucumber-chef config." method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY" def displayconfig boot details = { "root_dir" => Cucumber::Chef.root_dir, "home_dir" => Cucumber::Chef.home_dir, "log_file" => Cucumber::Chef.log_file, "artifacts_dir" => Cucumber::Chef.artifacts_dir, "config_rb" => Cucumber::Chef.config_rb, "labfile" => Cucumber::Chef.labfile, "chef_repo" => Cucumber::Chef.chef_repo, "chef_user" => Cucumber::Chef.chef_user, "chef_identity" => Cucumber::Chef.chef_identity, "bootstrap_user" => Cucumber::Chef.bootstrap_user, "bootstrap_user_home_dir" => Cucumber::Chef.bootstrap_user_home_dir, "bootstrap_identity" => Cucumber::Chef.bootstrap_identity, "lab_user" => Cucumber::Chef.lab_user, "lab_user_home_dir" => Cucumber::Chef.lab_user_home_dir, "lab_identity" => Cucumber::Chef.lab_identity, "lxc_user" => Cucumber::Chef.lxc_user, "lxc_user_home_dir" => Cucumber::Chef.lxc_user_home_dir, "lxc_identity" => Cucumber::Chef.lxc_identity, "chef_pre_11" => Cucumber::Chef.chef_pre_11 } max_key_length = details.collect{ |k,v| k.to_s.length }.max puts("-" * 80) say(Cucumber::Chef::Config.configuration.to_yaml, :bold) puts("-" * 80) details.each do |key,value| puts("%#{max_key_length}s = %s" % [key.downcase, value.inspect]) end puts("-" * 80) puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e.message) end ################################################################################ # CREATE ################################################################################ desc "create " , "Create a project template for testing an infrastructure." def create(project) create_project(project) root_dir = Cucumber::Chef.locate_parent(".chef") features_dir = File.join(root_dir, "features") feature = File.join(features_dir, "#{project}.feature") steps = File.join(features_dir, "step_definitions", "#{project}.steps") puts puts(set_color("Project created!", :green, true)) say("Please look at the README in '#{features_dir}/#{project}/', and the example features (#{File.basename(feature)}) and steps (#{File.basename(steps)}), which I have autogenerated for you.", :green) puts rescue Cucumber::Chef::Error => e $logger.fatal { e.backtrace.join("\n") } fatal(e) end ################################################################################ # DEPRECIATED TASKS ################################################################################ depreciated_tasks = { "teardown" => "You should execute the 'destroy' task instead.", "info" => "You should execute the 'status' task instead.", "test" => "You should execute 'cucumber' or 'rspec' directly." } depreciated_tasks.each do |old_task, message| desc old_task, "*DEPRECIATED* - #{message}" define_method(old_task) do puts puts(set_color("The '#{old_task}' task is *DEPRECIATED* - #{message}", :red, true)) puts end end ################################################################################ end CucumberChef.start