bin/cucumber-chef in cucumber-chef-1.0.3 vs bin/cucumber-chef in cucumber-chef-2.0.0.pre
- old
+ new
@@ -1,123 +1,453 @@
#!/usr/bin/env ruby
-require 'pathname'
-require 'fileutils'
require 'thor'
require 'cucumber-chef'
class CucumberChef < Thor
include Thor::Actions
no_tasks do
- def create_directory_structure(project_dir)
- %w{step_definitions support}.each do |dir|
- FileUtils.mkdir_p(project_dir + "features" + dir)
+
+ 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)
+
+ get_aws_credentials
+
+ 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 generate_project_skeleton(project_dir)
- template_dir = Pathname.new(__FILE__).parent.parent + 'lib' + 'cucumber' + 'chef' + 'templates'
- CucumberChef.source_root template_dir.realpath
+ 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" => 'README',
- "example_feature.erb" => 'features/example.feature',
- "example_step.erb" => 'features/step_definitions/example_step.rb',
- "env.rb" => "features/support/env.rb"
+ "readme.erb" => "features/#{project}/README",
+ "example_feature.erb" => "features/#{project}/#{project}.feature",
+ "example_steps.erb" => "features/#{project}/step_definitions/#{project}_steps.rb",
+ "env.rb" => "features/support/env.rb",
+ "readme-data_bags.erb" => "features/support/data_bags/README",
+ "readme-roles.erb" => "features/support/roles/README",
+ "readme-keys.erb" => "features/support/keys/README"
}
- templates.each do |filename, destination|
- template(filename, project_dir + destination)
+
+ templates.each do |source, destination|
+ template(source, File.join(destination_dir, destination))
end
end
- def config
- @config ||= begin
- options.test? ? Cucumber::Chef::Config.test_config : Cucumber::Chef::Config.new
+ def load_config
+
+ chef_repo = (Cucumber::Chef.locate_parent(".chef") rescue nil)
+ if ( !chef_repo || ( !File.exists?(chef_repo) || !File.directory?(chef_repo) ) )
+ fatal("Doesn't look like your inside a chef-repo! Please relocate to one and execute your command again!")
+ exit(255)
end
+
+ $logger = Cucumber::Chef::Logger.new
+ $logger.level = (Cucumber::Chef.is_rc? ? Cucumber::Chef::Logger::DEBUG : Cucumber::Chef::Logger::INFO)
+
+ message = "cucumber-chef v#{Cucumber::Chef::VERSION}"
+ puts(set_color(message, :green, true))
+
+ $logger.info { "================================================================================" }
+ $logger.info { message }
+ $logger.info { "UNAME: %s" % %x( uname -a ).chomp.strip }
+ $logger.info { "Ruby Version: #{RUBY_VERSION}" }
+ $logger.info { "Ruby Patch Level: #{RUBY_PATCHLEVEL}" }
+ $logger.info { "Ruby Platform: #{RUBY_PLATFORM}" }
+ $logger.info { "Ruby Engine: #{RUBY_ENGINE}" }
+ $logger.info { "================================================================================" }
+
+ @options.test? ? Cucumber::Chef::Config.test : Cucumber::Chef::Config.load
end
- def error(message)
- warn message
- exit 255
+ def fatal(message)
+ puts(set_color(message, :red, :bold))
+ exit(255)
end
+
+ def get_aws_credentials
+ say "OHAI!", :magenta
+ puts
+ say "Cucumber-Chef uses Amazon Web Services to build a test lab for automated infrastructure testing."
+ say "I need a few details before I can set up your test lab for you."
+ puts
+ say "We're going to use symmetric keys to authenticate with the AWS API."
+ say "First, I need your access key."
+ say "Your access key identifies you as you make API calls. It's not a secret."
+ say "You can find it under 'Access Credentials', on https://aws-portal.amazon.com/gp/aws/securityCredentials"
+ puts
+ @aws_access_key = ask "What is your AWS Access Key?", :bold
+ puts
+ say "Now I need your secret access key. This *is* a secret. The clue's in the name."
+ say "This is just a string of characters used to create the digital signature included in your API requests."
+ say "You can also find this under 'Access Credentials', on https://aws-portal.amazon.com/gp/aws/securityCredentials"
+ puts
+ @aws_secret_access_key = ask "What is your AWS Secret Access Key?", :bold
+ puts
+ say "Right. Now I need to know about the ssh key pair you want to use to connect to EC2 machines."
+ say "I need the name of the key pair. You can see this on the AWS management console, under Network & Security > Key Pairs"
+ puts
+ @aws_ssh_id = ask "What is your AWS Key Pair called?", :bold
+ puts
+ say "I also need to know what the ssh key is called - the actual name of the file on your local machine, eg #{@aws_ssh_id}.pem"
+ puts
+ @aws_ssh_key = ask "What's the filename of your ssh key?", :bold
+ puts
+ say "And, finally, I need to know where you keep it, on the file system. Often this is ~/.ssh"
+ puts
+ @aws_ssh_key_dir = ask "What directory contains your ssh key?", :bold
+ puts
+ say "OK, nearly there. AWS uses different keys depending on which region you use."
+ say "For example, 'us-east', 'us-west', or 'eu-west'"
+ puts
+ @region = ask "Which region are you using?", :bold
+ puts
+ say("One last thing. If your using librarian-chef, we want to be sure all the hooks are in place.")
+ puts
+ @librarian_chef = yes?("Does this chef-repo use librarian-chef?", :bold)
+ puts
+ say "Awesome. Thank you!"
+ puts
+ end
+
end
- desc "project <project name>" , "Create a project template for testing an infrastructure"
- def project(project_name)
- @project = project_name
- project_dir = Pathname.new(".") + "cucumber-chef" + @project
- create_directory_structure(project_dir)
- generate_project_skeleton(project_dir)
+################################################################################
+
+ desc "init", "Initalize cucumber-chef configuration"
+ def init
+ initalize_config
end
- desc "setup", "Set up a cucumber-chef test lab in Amazon EC2"
- method_option :test, :type => :boolean
+################################################################################
+
+ desc "setup", "Setup cucumber-chef test lab in Amazon EC2"
+ method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY"
def setup
- begin
- config.verify
- $stdout.sync
- provisioner = ::Cucumber::Chef::Provisioner.new
- server = provisioner.build_test_lab(config, $stdout)
- sleep(10)
- provisioner.upload_cookbook(config)
- provisioner.upload_role(config)
- provisioner.bootstrap_node(server.dns_name, config)
- rescue ::Cucumber::Chef::Error => err
- error(err.message)
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new)
+ if (server = test_lab.create)
+ if (provisioner = Cucumber::Chef::Provisioner.new(server))
+
+ provisioner.build
+
+ puts
+ puts("Your cucumber-chef test lab has now been provisioned!")
+ puts
+ puts("Be sure to log into the chef-server webui and change the default credentials.")
+ puts
+ puts(" Chef-Server WebUI:")
+ puts(" http://#{server.public_ip_address}:4040/")
+ puts(" Username:")
+ puts(" admin")
+ puts(" Password:")
+ puts(" #{Cucumber::Chef::Provisioner::PASSWORD}")
+
+ 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
- desc "connect", "Connect to a container in your test lab"
- def connect
- puts "Not implemented. For now, find the IP of your test lab using the info task, and connect manually."
+################################################################################
+
+ desc "teardown", "Teardown cucumber-chef test lab in Amazon EC2"
+ method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY"
+ def teardown
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new)
+ if yes?(set_color("Are you sure you want to teardown your cucumber-chef test lab?", :red, true))
+ count_down_colors = { 5 => :green, 4 => :yellow, 3 => :yellow, 2 => :red, 1 => :red }
+ puts
+ puts(set_color("You have 5 seconds to abort!", :green, true))
+ puts
+ print(set_color("Self-destructing in", :green, true))
+ 5.downto(1) do |x|
+ print(set_color("...#{x}", count_down_colors[x], true))
+ sleep(1)
+ end
+ puts(set_color("...BOOM!", :red, true))
+ puts
+ test_lab.destroy
+ else
+ puts
+ puts(set_color("Whew! That was close!", :green, true))
+ end
+ else
+ puts(set_color("Could not find a cucumber-chef test lab to teardown!", :red, true))
+ end
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
end
- desc "displayconfig", "Display the current config from knife.rb"
- method_option :test, :type => :boolean
- def displayconfig
- puts config.list.join("\n")
- config.verify
- rescue ::Cucumber::Chef::Error => err
- error(err.message)
+################################################################################
+
+ desc "up", "Startup the cucumber-chef test lab"
+ def up
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new)
+ test_lab.start
+ else
+ puts(set_color("Could not find a cucumber-chef test lab to startup!", :red, true))
+ end
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
end
- desc "info", "Display information about the current test lab"
- method_option :test, :type => :boolean
- def info
- config.verify
- lab = Cucumber::Chef::TestLab.new(config)
- puts lab.info
- rescue ::Cucumber::Chef::Error => err
- error(err.message)
+################################################################################
+
+ desc "down", "Shutdown the cucumber-chef test lab"
+ def down
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new)
+ test_lab.stop
+ else
+ puts(set_color("Could not find a cucumber-chef test lab to shutdown!", :red, true))
+ end
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
end
- desc "destroy", "Destroy running test labs"
- method_option :test, :type => :boolean
- def destroy
- config.verify
- lab = Cucumber::Chef::TestLab.new(config)
- lab.destroy
+################################################################################
+
+ desc "ssh [container]", "SSH to cucumber-chef test lab or [container] if specified."
+ method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY"
+ def ssh(*args)
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new) && (test_lab.labs_running.count > 0)
+ ssh = Cucumber::Chef::SSH.new
+
+ if args.size == 0
+ ssh.config[:host] = test_lab.labs_running.first.public_ip_address
+ ssh.config[:ssh_user] = "ubuntu"
+ ssh.config[:identity_file] = Cucumber::Chef.locate(:file, ".cucumber-chef", "id_rsa-#{ssh.config[:ssh_user]}")
+
+ puts([set_color("Attempting SSH connection to cucumber-chef '", :blue, true), set_color("test lab", :cyan, true), set_color("'...", :blue, true)].join)
+ ssh.console
+ else
+ container = args[0]
+
+ ssh.config[:proxy] = true
+ ssh.config[:proxy_host] = test_lab.labs_running.first.public_ip_address
+ ssh.config[:proxy_ssh_user] = "ubuntu"
+ ssh.config[:proxy_identity_file] = Cucumber::Chef.locate(:file, ".cucumber-chef", "id_rsa-#{ssh.config[:proxy_ssh_user]}")
+
+ ssh.config[:host] = container
+ ssh.config[:ssh_user] = "root"
+ ssh.config[:identity_file] = Cucumber::Chef.locate(:file, ".cucumber-chef", "id_rsa-#{ssh.config[:proxy_ssh_user]}")
+
+ puts([set_color("Attempting SSH connection to cucumber-chef container '", :blue, true), set_color(container, :cyan, true), set_color("'...", :blue, true)].join)
+ ssh.console
+ end
+
+ else
+ puts(set_color("No cucumber-chef test labs available to ssh to!", :red, true))
+ end
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
end
- desc "upload <project name>", "Upload a cucumber-chef test suite to the test lab platform"
- def upload(project_name)
- project_dir = Pathname.new(".") + "cucumber-chef" + project_name
- unless File.exists?(project_dir)
- raise "Project dir '#{project_dir}' does not exist."
+################################################################################
+
+ desc "diagnose <container>", "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)
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new) && (test_lab.labs_running.count > 0)
+ ssh = Cucumber::Chef::SSH.new
+
+ ssh.config[:proxy] = true
+ ssh.config[:proxy_host] = test_lab.labs_running.first.public_ip_address
+ ssh.config[:proxy_ssh_user] = "ubuntu"
+ ssh.config[:proxy_identity_file] = Cucumber::Chef.locate(:file, ".cucumber-chef", "id_rsa-#{ssh.config[:proxy_ssh_user]}")
+
+ ssh.config[:host] = container
+ ssh.config[:ssh_user] = "root"
+ ssh.config[:identity_file] = Cucumber::Chef.locate(:file, ".cucumber-chef", "id_rsa-#{ssh.config[:proxy_ssh_user]}")
+
+ 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))
+ ssh.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))
+ ssh.exec("[[ -e /var/log/chef/client.log ]] && tail -n #{@options.lines} /var/log/chef/client.log")
+ end
end
- config.verify
- runner = Cucumber::Chef::TestRunner.new(project_dir, config)
- runner.upload_project
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
end
- desc "test", "Run a cucumber-chef test suite from a workstation."
- def test(project_name)
- project_dir = Pathname.new(".") + "cucumber-chef" + project_name
- unless File.exists?(project_dir)
- raise "Project dir '#{project_dir}' does not exist."
+################################################################################
+
+ desc "displayconfig", "Display the current cucumber-chef config."
+ method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY"
+ def displayconfig
+ load_config
+
+ puts
+ say(Cucumber::Chef::Config.configuration.to_yaml, :bold)
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e.message)
+ end
+
+################################################################################
+
+ desc "ps [ps-options]", "Snapshot of the current cucumber-chef test lab container processes."
+ def ps(*args)
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new) && (test_lab.labs_running.count > 0)
+ ssh = Cucumber::Chef::SSH.new
+
+ ssh.config[:host] = test_lab.labs_running.first.public_ip_address
+ ssh.config[:ssh_user] = "ubuntu"
+ ssh.config[:identity_file] = Cucumber::Chef.locate(:file, ".cucumber-chef", "id_rsa-#{ssh.config[:ssh_user]}")
+
+ puts(set_color("Getting container processes from cucumber-chef test lab...", :blue, true))
+ puts
+ puts(set_color("============================================================================", :bold))
+ ssh.exec("lxc-ps --lxc -- #{args.join(" ")}")
+ print("\n")
end
- config.verify
- runner = Cucumber::Chef::TestRunner.new(project_dir, config)
- runner.run
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
end
+
+################################################################################
+
+ desc "info", "Display information about the current test lab."
+ method_option :test, :type => :boolean, :desc => "INTERNAL USE ONLY"
+ def info
+ load_config
+
+ puts
+ if (test_lab = Cucumber::Chef::TestLab.new)
+ test_lab.info
+ end
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e.message)
+ end
+
+################################################################################
+
+ desc "create <project>" , "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
+
+################################################################################
+
+ desc "test [cucumber-options]", "Test a project using the cucumber-chef test suite."
+ method_option :destroy, :type => :boolean, :desc => "destroy all containers before test run", :aliases => "-z", :default => false
+ def test(*args)
+ load_config
+
+ puts
+ root_path = Cucumber::Chef.locate_parent(".cucumber-chef")
+ features_path = File.expand_path(File.join(root_path, "features"))
+
+ unless (File.exists?(features_path) && File.directory?(features_path))
+ raise Error, "Features directory '#{features_path}' does not exist."
+ else
+ puts("Using features directory: #{features_path}")
+ end
+
+ runner = Cucumber::Chef::TestRunner.new(features_path)
+ runner.run(@options.destroy?, args)
+ puts
+
+ rescue Cucumber::Chef::Error => e
+ $logger.fatal { e.backtrace.join("\n") }
+ fatal(e)
+ end
+
+################################################################################
+
end
+
CucumberChef.start