# # Author:: Fletcher Nichol () # # Copyright (C) 2013, Fletcher Nichol # # 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 "rubygems/gem_runner" require "thor/group" module Kitchen module Generator # A project initialization generator, to help prepare a cookbook project # for testing with Kitchen. # # @author Fletcher Nichol class Init < Thor::Group include Thor::Actions class_option :driver, type: :array, aliases: "-D", default: %w{kitchen-vagrant}, desc: <<-D.gsub(/^\s+/, "").tr("\n", " ") One or more Kitchen Driver gems to be installed or added to a Gemfile D class_option :provisioner, type: :string, aliases: "-P", default: "chef_solo", desc: <<-D.gsub(/^\s+/, "").tr("\n", " ") The default Kitchen Provisioner to use D class_option :create_gemfile, type: :boolean, default: false, desc: <<-D.gsub(/^\s+/, "").tr("\n", " ") Whether or not to create a Gemfile if one does not exist. Default: false D # Invoke the command. def init self.class.source_root(Kitchen.source_root.join("templates", "init")) create_kitchen_yaml create_chefignore prepare_rakefile prepare_thorfile create_test_dir prepare_gitignore prepare_gemfile display_bundle_message end private # Creates the `.kitchen.yml` file. # # @api private def create_kitchen_yaml cookbook_name = if File.exist?(File.expand_path("metadata.rb")) MetadataChopper.extract("metadata.rb").first end run_list = cookbook_name ? "recipe[#{cookbook_name}::default]" : nil driver_plugin = Array(options[:driver]).first || "dummy" template("kitchen.yml.erb", "kitchen.yml", driver_plugin: driver_plugin.sub(/^kitchen-/, ""), provisioner: options[:provisioner], run_list: Array(run_list)) end # Creates the `chefignore` file. # # @api private def create_chefignore template("chefignore.erb", "chefignore") end # @return [true,false] whether or not a Gemfile needs to be initialized # @api private def init_gemfile? File.exist?(File.join(destination_root, "Gemfile")) || options[:create_gemfile] end # @return [true,false] whether or not a Rakefile needs to be initialized # @api private def init_rakefile? File.exist?(File.join(destination_root, "Rakefile")) && not_in_file?("Rakefile", %r{require 'kitchen/rake_tasks'}) end # @return [true,false] whether or not a Thorfile needs to be initialized # @api private def init_thorfile? File.exist?(File.join(destination_root, "Thorfile")) && not_in_file?("Thorfile", %r{require 'kitchen/thor_tasks'}) end # @return [true,false] whether or not a test directory needs to be # initialized # @api private def init_test_dir? Util.list_directory("test/integration/").select { |d| File.directory?(d) }.empty? end # @return [true,false] whether or not a `.gitignore` file needs to be # initialized # @api private def init_git? File.directory?(File.join(destination_root, ".git")) end # Prepares a Rakefile. # # @api private def prepare_rakefile return unless init_rakefile? rakedoc = <<-RAKE.gsub(/^ {10}/, "") begin require_relative '../rake_tasks' Kitchen::RakeTasks.new rescue LoadError puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] end RAKE append_to_file(File.join(destination_root, "Rakefile"), rakedoc) end # Prepares a Thorfile. # # @api private def prepare_thorfile return unless init_thorfile? thordoc = <<-THOR.gsub(/^ {10}/, "") begin require_relative '../thor_tasks' Kitchen::ThorTasks.new rescue LoadError puts '>>>>> Kitchen gem not loaded, omitting tasks' unless ENV['CI'] end THOR append_to_file(File.join(destination_root, "Thorfile"), thordoc) end # Create the default test directory # # @api private def create_test_dir empty_directory "test/integration/default" if init_test_dir? end # Prepares the .gitignore file # # @api private def prepare_gitignore return unless init_git? append_to_gitignore(".kitchen/") append_to_gitignore(".kitchen.local.yml") end # Appends a line to the .gitignore file. # # @api private def append_to_gitignore(line) create_file(".gitignore") unless File.exist?(File.join(destination_root, ".gitignore")) if IO.readlines(File.join(destination_root, ".gitignore")).grep(/^#{line}/).empty? append_to_file(".gitignore", "#{line}\n") end end # Prepares a Gemfile. # # @api private def prepare_gemfile return unless init_gemfile? create_gemfile_if_missing add_gem_to_gemfile end # Creates a Gemfile if missing # # @api private def create_gemfile_if_missing unless File.exist?(File.join(destination_root, "Gemfile")) create_file("Gemfile", %{source "https://rubygems.org"\n\n}) end end # Appends entries to a Gemfile. # # @api private def add_gem_to_gemfile if not_in_file?("Gemfile", /gem ('|")test-kitchen('|")/) append_to_file("Gemfile", %{gem "test-kitchen"\n}) @display_bundle_msg = true end end # Displays a bundle warning message to the user. # # @api private def display_bundle_message if @display_bundle_msg say "You must run `bundle install' to fetch any new gems.", :red end end # Determines whether or not a pattern is found in a file. # # @param filename [String] filename to read # @param regexp [Regexp] a regular expression # @return [true,false] whether or not a pattern is found in a file # @api private def not_in_file?(filename, regexp) IO.readlines(File.join(destination_root, filename)).grep(regexp).empty? end # Save off any Bundler/Ruby-related environment variables so that the # yielded block can run "bundler-free" (and restore at the end). # # @api private def unbundlerize keys = ENV.keys.select { |key| key =~ /^BUNDLER?_/ } + %w{RUBYOPT} keys.each { |key| ENV["__#{key}"] = ENV[key]; ENV.delete(key) } yield keys.each { |key| ENV[key] = ENV.delete("__#{key}") } end end end end