require "chef" require "chef/cookbook_uploader" require 'chef/knife' require 'chef/knife/bootstrap' require 'chef/knife/core/bootstrap_context' require 'chef/knife/ssh' require 'socket' require 'net/ssh/multi' require "fog" require 'readline' require 'stringio' module Cucumber module Chef class Error < StandardError ; end class ConfigError < Error ; end class Config KEYS = %w[node_name chef_server_url client_key validation_key validation_client_name] KNIFE_KEYS = %w[aws_access_key_id aws_secret_access_key region availability_zone aws_ssh_key_id identity_file] def config full_path = Dir.pwd.split(File::SEPARATOR) (full_path.length - 1).downto(0) do |i| knife_file = File.join(full_path[0..i] + [".chef", "knife.rb"]) if File.exist?(knife_file) ::Chef::Config.from_file(knife_file) return ::Chef::Config end end raise ConfigError.new("Couldn't find knife.rb") end def display current_config = config values, missing_keys = [], [] KEYS.each do |key| value = current_config[key] if value && value != "" values << "#{key}: #{value}" else missing_keys << key end end KNIFE_KEYS.each do |key| if value = current_config[:knife][key.to_sym] values << "knife[:#{key}]: #{value}" else missing_keys << "knife[:#{key}]" end end [values, missing_keys] end end class ProvisionerError < Error ; end class Provisioner def initialize @cookbook_path = File.join(File.dirname(__FILE__), "../../cookbooks/cucumber-chef") end def config Config.new.config end def running_labs(connection, mode) connection.servers.select {|s| s.tags['cucumber-chef'] == mode && s.state == 'running'} end def lab_exists?(connection, mode) number_of_labs = running_labs(connection, mode).size if number_of_labs > 0 return true else return false end end def tag_server(connection, id, tag) t = connection.tags.new t.resource_id = id t.key = "cucumber-chef" t.value = tag t.save end def bootstrap_node(dns_name, config) template_file = File.join(File.dirname(__FILE__), "chef/templates/ubuntu10.04-gems.erb") bootstrap = ::Chef::Knife::Bootstrap.new @stdout, @stderr, @stdout = StringIO.new, StringIO.new, StringIO.new ui = ::Chef::Knife::UI.new(@stdout, @stderr, @stdout, bootstrap.config) bootstrap.ui = ui bootstrap.name_args = [dns_name] bootstrap.config[:run_list] = "role[test_lab_test]" bootstrap.config[:ssh_user] = "ubuntu" bootstrap.config[:identity_file] = config[:knife][:identity_file] bootstrap.config[:chef_node_name] = "cucumber-chef-test-lab" bootstrap.config[:use_sudo] = true bootstrap.config[:template_file] = template_file bootstrap.config[:validation_client_name] = config["validation_client_name"] bootstrap.config[:validation_key] = config["validation_key"] bootstrap.config[:chef_server_url] = config["chef_server_url"] bootstrap end def build_controller(dns_name, config) template_file = File.join(File.dirname(__FILE__), "chef/templates/controller.erb") bootstrap = ::Chef::Knife::Bootstrap.new @stdout, @stderr, @stdout = StringIO.new, StringIO.new, StringIO.new ui = ::Chef::Knife::UI.new(@stdout, @stderr, @stdout, bootstrap.config) bootstrap.ui = ui bootstrap.name_args = [dns_name] bootstrap.config[:ssh_user] = "ubuntu" bootstrap.config[:identity_file] = config[:knife][:identity_file] bootstrap.config[:chef_node_name] = "cucumber-chef-controller" bootstrap.config[:use_sudo] = true bootstrap.config[:template_file] = template_file bootstrap.config[:validation_client_name] = config["validation_client_name"] bootstrap.config[:validation_key] = config["validation_key"] bootstrap.config[:chef_server_url] = config["chef_server_url"] bootstrap end def verify_opscode_platform_credentials(config) username = config['node_name'] if username req = Net::HTTP.new('community.opscode.com', 80) code = req.request_head("/users/#{username}").code end if username == "" || code != "200" raise ProvisionerError.new("Invalid Opscode platform credentials. Please check.") end end def verify_aws_credentials(config) compute = Fog::Compute.new(:provider => 'AWS', :aws_access_key_id => config[:knife][:aws_access_key_id], :aws_secret_access_key => config[:knife][:aws_secret_access_key]) compute.describe_availability_zones rescue Fog::Service::Error => err raise ProvisionerError.new("Invalid AWS credentials. Please check.") end def upload_cookbook(config) version_loader = ::Chef::Cookbook::CookbookVersionLoader.new(@cookbook_path) version_loader.load_cookbooks uploader = ::Chef::CookbookUploader.new(version_loader.cookbook_version, @cookbook_path) uploader.upload_cookbook end def upload_role(config) role_path = File.join(@cookbook_path, "roles") ::Chef::Config[:role_path] = role_path role = ::Chef::Role.from_disk("test_lab_test") role.save role = ::Chef::Role.from_disk("controller") role.save end def build_test_lab(config, output) connection = Fog::Compute.new(:provider => 'AWS', :aws_access_key_id => config[:knife][:aws_access_key_id], :aws_secret_access_key => config[:knife][:aws_secret_access_key], :region => config[:knife][:region]) mode = config["mode"] if lab_exists?(connection, mode) raise ProvisionerError.new("A test lab already exists using the AWS credentials you supplied") end ami = connection.images.get("ami-339ca947") server_def = { :image_id => "ami-339ca947", :groups => "default", :flavor_id => "m1.small", :key_name => config[:knife][:aws_ssh_key_id], :availability_zone => config[:knife][:availability_zone], :tags => {"purpose" => "cucumber-chef"}, :identity_file => config[:knife][:identity_file] } server = connection.servers.create(server_def) output.puts "Provisioning cucumber-chef test lab platform." output.print "Waiting for server" server.wait_for { output.print "."; ready? } output.puts("\n") tag_server(connection, server.id, config["mode"]) output.puts "Instance ID: #{server.id} ; IP Address #{server.public_ip_address}" output.puts "Platform provisioned. Run cucumber-chef project to get started." server end end end end begin require 'cucumber/chef/version' rescue LoadError => e dep = e.message.split.last puts "You don't appear to have #{dep} installed." puts "Perhaps run `gem bundle` or `gem install #{dep}`?" exit 2 end