diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3f42dbfb --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "lib/knife-solo/resources/patch_cookbooks/chef-solo-search"] + path = lib/knife-solo/resources/patch_cookbooks/chef-solo-search + url = https://github.com/edelight/chef-solo-search.git diff --git a/CHANGELOG.md b/CHANGELOG.md index 9711c6be..7b3ac15c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,12 @@ # 0.3.0 / _In progress_ +**NOTE**: This release includes breaking changes. See [upgrade instructions](https://github.com/matschaffer/knife-solo/wiki/Upgrading-to-0.3.0) for more information. + ## Changes and new features -* Moved root path configuration into `knife[:solo_path]` and use $HOME/chef-solo by default ([197]) +* [BREAKING] Generate solo.rb based on knife.rb settings ([199]) +* [BREAKING] Set root path with `--provisioning-path` or `knife[:provisioning_path]` and use ~/chef-solo by default ([1], [86], [125], [128], [177], [197]) +* Read protect the provision directory from the world ([1]) * `--prerelease` option to allow pre-release versions of chef omnibus or rubygem to be installed ([205]) * Prepare/bootstrap now installs the same version of Chef that the workstation is running ([186]) * Remove hard dependency on Librarian-Chef ([211]) @@ -28,9 +32,15 @@ * [Naoya Ito][naoya] * [David Radcliffe][dwradcliffe] +[1]: https://github.com/matschaffer/knife-solo/issues/1 +[86]: https://github.com/matschaffer/knife-solo/issues/86 +[125]: https://github.com/matschaffer/knife-solo/issues/125 +[128]: https://github.com/matschaffer/knife-solo/issues/128 +[177]: https://github.com/matschaffer/knife-solo/issues/177 [185]: https://github.com/matschaffer/knife-solo/issues/185 [186]: https://github.com/matschaffer/knife-solo/issues/186 [197]: https://github.com/matschaffer/knife-solo/issues/197 +[199]: https://github.com/matschaffer/knife-solo/issues/199 [200]: https://github.com/matschaffer/knife-solo/issues/200 [204]: https://github.com/matschaffer/knife-solo/issues/204 [205]: https://github.com/matschaffer/knife-solo/issues/205 @@ -62,7 +72,6 @@ * Drop support for Debian 5.0 Lenny (#172) * Integration tests for Debian 6 and 7 (74c6ed1 - f299a6) * Travis tests for both Chef 10 and 11 (#183) -* Remove solo.rb and transfer cookbooks to user-owned path (#1, #86, #125, #128, #177). See https://github.com/matschaffer/knife-solo/wiki/Upgrading-to-0.2.0 ## Fixes diff --git a/Manifest.txt b/Manifest.txt index 380444e3..12b0c6b3 100644 --- a/Manifest.txt +++ b/Manifest.txt @@ -5,8 +5,6 @@ Rakefile lib/chef/knife/bootstrap_solo.rb lib/chef/knife/cook.rb lib/chef/knife/kitchen.rb -lib/chef/knife/patches/parser.rb -lib/chef/knife/patches/search.rb lib/chef/knife/prepare.rb lib/chef/knife/solo_bootstrap.rb lib/chef/knife/solo_clean.rb @@ -20,12 +18,13 @@ lib/knife-solo/bootstraps/darwin.rb lib/knife-solo/bootstraps/freebsd.rb lib/knife-solo/bootstraps/linux.rb lib/knife-solo/bootstraps/sun_os.rb -lib/knife-solo/config.rb lib/knife-solo/deprecated_command.rb lib/knife-solo/gitignore.rb lib/knife-solo/info.rb lib/knife-solo/node_config_command.rb -lib/knife-solo/resources/solo.rb +lib/knife-solo/resources/knife.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search +lib/knife-solo/resources/solo.rb.erb lib/knife-solo/ssh_command.rb lib/knife-solo/tools.rb test/bootstraps_test.rb @@ -49,7 +48,6 @@ test/integration/ubuntu10_04_test.rb test/integration/ubuntu12_04_bootstrap_test.rb test/integration/ubuntu12_04_test.rb test/integration_helper.rb -test/knife-solo/config_test.rb test/knife_bootstrap_test.rb test/minitest/parallel.rb test/node_config_command_test.rb @@ -77,3 +75,26 @@ test/support/test_case.rb test/support/validation_helper.rb test/test_helper.rb test/tools_test.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/.travis.yml +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/CHANGELOG +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/LICENSE +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/NOTICE +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/README.md +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/libraries/search.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/libraries/search/overrides.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/libraries/search/parser.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene.treetop +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/lucene_nodes.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/libraries/vendor/chef/solr_query/query_transform.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/metadata.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/recipes/default.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/Gemfile +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/node/alpha.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/node/beta.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/node/without_json_class.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/users/jerry.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/users/lea.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/users/mike.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/data/data_bags/users/tom.json +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/test_data_bags.rb +lib/knife-solo/resources/patch_cookbooks/chef-solo-search/tests/test_search.rb diff --git a/README.rdoc b/README.rdoc index 368567fa..895c9295 100644 --- a/README.rdoc +++ b/README.rdoc @@ -35,12 +35,13 @@ The init command simply takes a name of the directory to store the kitchen struc Currently the directory structure looks like this, but could change as development continues. mychefrepo/ + ├── .chef + │ └── knife.rb ├── cookbooks ├── data_bags ├── nodes ├── roles - ├── site-cookbooks - └── solo.rb + └── site-cookbooks === Prepare command @@ -71,7 +72,7 @@ The cook command uploads the current kitchen to the server and runs chef-solo on This uploads all of your cookbooks in addition to a patch that allows you to use data_bags in a read-only fashion from the +data_bags+ folder. -This also supports encrypted data bags. To use them, place your key in +data_bag_key+ in the root of your kitchen (or if you move it make sure to update +solo.rb+ to reflect the new path). +This also supports encrypted data bags. To use them, set the path to your key with +encrypted_data_bag_secret+ in .chef/knife.rb. The knife command for creating encrypted data bags doesn't work well without a Chef server, so use {this gist}[https://gist.github.com/2896172] as an example on how to create encrypted data bag items on your local file system. @@ -99,9 +100,9 @@ The clean command removes an uploaded kitchen completely from the target host. T The cook command will work on Windows node if you meet the following howto: -==== Init then tweak +==== Init as normally -- run knife solo init then edit solo.rb to use Windows path-naming (see https://gist.github.com/1773854) +- run knife solo init ==== Prepare the node manually diff --git a/Rakefile b/Rakefile index 814632e6..835eee2c 100644 --- a/Rakefile +++ b/Rakefile @@ -5,6 +5,7 @@ require File.join(File.dirname(__FILE__), 'lib', 'knife-solo', 'info') MANIFEST_IGNORES = %w[ .travis.yml .gitignore + .gitmodules Gemfile Gemfile.lock Manifest.txt @@ -24,10 +25,11 @@ namespace :manifest do desc 'Updates Manifest.txt with a list of files from git' task :update do - git_files = `git ls-files`.split("\n") + git_files = `git ls-files`.split("\n") + submodule_files = `git submodule foreach -q 'for f in $(git ls-files); do echo $path/$f; done'`.split("\n") File.open('Manifest.txt', 'w') do |f| - f.puts((git_files - MANIFEST_IGNORES).join("\n")) + f.puts((git_files + submodule_files - MANIFEST_IGNORES).join("\n")) end end end diff --git a/knife-solo.gemspec b/knife-solo.gemspec index a41a4455..ff433c1b 100644 --- a/knife-solo.gemspec +++ b/knife-solo.gemspec @@ -34,4 +34,5 @@ Gem::Specification.new do |s| s.add_dependency 'chef', chef_version s.add_dependency 'net-ssh', '>= 2.2.2', '< 3.0' + s.add_dependency 'erubis', '~> 2.7.0' end diff --git a/lib/chef/knife/patches/parser.rb b/lib/chef/knife/patches/parser.rb deleted file mode 100644 index 69b07e61..00000000 --- a/lib/chef/knife/patches/parser.rb +++ /dev/null @@ -1,223 +0,0 @@ -# -# Copyright 2011, edelight GmbH -# -# Authors: -# Markus Korn -# -# 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 'treetop' -require 'chef/solr_query/query_transform' - -# mock QueryTransform such that we can access the location of the lucene grammar -class Chef - class SolrQuery - class QueryTransform - def self.base_path - class_variable_get(:@@base_path) - end - end - end -end - -def build_flat_hash(hsh, prefix="") - result = {} - hsh.each_pair do |key, value| - if value.kind_of?(Hash) - result.merge!(build_flat_hash(value, "#{prefix}#{key}_")) - else - result[prefix+key] = value - end - end - result -end - -module Lucene - - class Term < Treetop::Runtime::SyntaxNode - # compares a query value and a value, tailing '*'-wildcards are handled correctly. - # Value can either be a string or an array, all other objects are converted - # to a string and than checked. - def match( value ) - if value.is_a?(Array) - value.any?{ |x| self.match(x) } - else - File.fnmatch(self.text_value, value.to_s) - end - end - end - - class Field < Treetop::Runtime::SyntaxNode - # simple field -> value matches, supporting tailing '*'-wildcards in keys - # as well as in values - def match( item ) - keys = self.elements[0].match(item) - if keys.nil? - false - else - keys.any?{ |key| self.elements[1].match(item[key]) } - end - end - end - - # we don't support range matches - # range of integers would be easy to implement - # but string ranges are hard - class FiledRange < Treetop::Runtime::SyntaxNode - end - - # we handle '[* TO *]' as a special case since it is common in - # cookbooks for matching the existence of keys - class InclFieldRange - def match(item) - field = self.elements[0].text_value - range_start = self.elements[1].transform - range_end = self.elements[2].transform - if range_start == "*" and range_end == "*" - !!item[field] - else - raise "Ranges not really supported yet" - end - end - end - - class ExclFieldRange < FieldRange - end - - class RangeValue < Treetop::Runtime::SyntaxNode - end - - class FieldName < Treetop::Runtime::SyntaxNode - def match( item ) - if self.text_value.count("_") > 0 - item.merge!(build_flat_hash(item)) - end - if self.text_value.end_with?("*") - part = self.text_value.chomp("*") - item.keys.collect{ |key| key.start_with?(part)? key: nil}.compact - else - if item[self.text_value] - [self.text_value,] - else - nil - end - end - end - end - - class Body < Treetop::Runtime::SyntaxNode - def match( item ) - self.elements[0].match( item ) - end - end - - class Group < Treetop::Runtime::SyntaxNode - def match( item ) - self.elements[0].match(item) - end - end - - class BinaryOp < Treetop::Runtime::SyntaxNode - def match( item ) - self.elements[1].match( - self.elements[0].match(item), - self.elements[2].match(item) - ) - end - end - - class OrOperator < Treetop::Runtime::SyntaxNode - def match( cond1, cond2 ) - cond1 or cond2 - end - end - - class AndOperator < Treetop::Runtime::SyntaxNode - def match( cond1, cond2 ) - cond1 and cond2 - end - end - - # we don't support fuzzy string matching - class FuzzyOp < Treetop::Runtime::SyntaxNode - end - - class BoostOp < Treetop::Runtime::SyntaxNode - end - - class FuzzyParam < Treetop::Runtime::SyntaxNode - end - - class UnaryOp < Treetop::Runtime::SyntaxNode - def match( item ) - self.elements[0].match( - self.elements[1].match(item) - ) - end - end - - class NotOperator < Treetop::Runtime::SyntaxNode - def match( cond ) - not cond - end - end - - class RequiredOperator < Treetop::Runtime::SyntaxNode - end - - class ProhibitedOperator < Treetop::Runtime::SyntaxNode - end - - class Phrase < Treetop::Runtime::SyntaxNode - # a quoted ::Term - def match( value ) - self.elements[0].match(value) - end - end -end - -class Query - # initialize the parser by using the grammar shipped with chef - @@grammar = File.join(Chef::SolrQuery::QueryTransform.base_path, "lucene.treetop") - Treetop.load(@@grammar) - @@parser = LuceneParser.new - - def self.parse(data) - # parse the query into a query tree - if data.nil? - data = "*:*" - end - tree = @@parser.parse(data) - if tree.nil? - msg = "Parse error at offset: #{@@parser.index}\n" - msg += "Reason: #{@@parser.failure_reason}" - raise "Query #{data} is not supported: #{msg}" - end - self.clean_tree(tree) - tree - end - - private - - def self.clean_tree(root_node) - # remove all SyntaxNode elements from the tree, we don't need them as - # the related ruby class already knowns what to do. - return if root_node.elements.nil? - root_node.elements.delete_if do |node| - node.class.name == "Treetop::Runtime::SyntaxNode" - end - root_node.elements.each { |node| self.clean_tree(node) } - end -end - diff --git a/lib/chef/knife/patches/search.rb b/lib/chef/knife/patches/search.rb deleted file mode 100644 index b528c716..00000000 --- a/lib/chef/knife/patches/search.rb +++ /dev/null @@ -1,110 +0,0 @@ -# -# Copyright 2011, edelight GmbH -# -# Authors: -# Markus Korn -# -# 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. -# - -if Chef::Config[:solo] - - if (defined? require_relative).nil? - # defenition of 'require_relative' for ruby < 1.9, found on stackoverflow.com - def require_relative(relative_feature) - c = caller.first - fail "Can't parse #{c}" unless c.rindex(/:\d+(:in `.*')?$/) - file = $` - if /\A\((.*)\)/ =~ file # eval, etc. - raise LoadError, "require_relative is called in #{$1}" - end - absolute = File.expand_path(relative_feature, File.dirname(file)) - require absolute - end - end - - require_relative 'parser.rb' - - class Chef - module Mixin - module Language - - # Overwrite the search method of recipes to operate locally by using - # data found in data_bags. - # Only very basic lucene syntax is supported and also sorting the result - # is not implemented, if this search method does not support a given query - # an exception is raised. - # This search() method returns a block iterator or an Array, depending - # on how this method is called. - def search(obj, query=nil, sort=nil, start=0, rows=1000, &block) - if !sort.nil? - raise "Sorting search results is not supported" - end - @_query = Query.parse(query) - if @_query.nil? - raise "Query #{query} is not supported" - end - @_result = [] - - case obj - when :node - search_nodes(start, rows, &block) - when :role - search_roles(start, rows, &block) - else - search_data_bag(obj, start, rows, &block) - end - - - if block_given? - pos = 0 - while (pos >= start and pos < (start + rows) and pos < @_result.size) - yield @_result[pos] - pos += 1 - end - else - return @_result.slice(start, rows) - end - end - - def search_nodes(start, rows, &block) - Dir.glob(File.join(Chef::Config[:data_bag_path], "node", "*.json")).map do |f| - # parse and hashify the node - node = JSON.parse(IO.read(f)) - if @_query.match(node.to_hash) - @_result << node - end - end - end - - def search_roles(start, rows, &block) - raise "Role searching not implemented" - end - - def search_data_bag(bag_name, start, rows, &block) - secret_path = Chef::Config[:encrypted_data_bag_secret] - data_bag(bag_name.to_s).each do |bag_item_id| - if secret_path && secret = Chef::EncryptedDataBagItem.load_secret(secret_path) - bag_item = Chef::EncryptedDataBagItem.load(bag_name, bag_item_id, secret) - else - bag_item = data_bag_item(bag_name.to_s, bag_item_id) - end - if @_query.match(bag_item) - @_result << bag_item - end - end - end - end - end - end -end diff --git a/lib/chef/knife/solo_clean.rb b/lib/chef/knife/solo_clean.rb index 5db89370..4a3de38e 100644 --- a/lib/chef/knife/solo_clean.rb +++ b/lib/chef/knife/solo_clean.rb @@ -1,24 +1,36 @@ require 'chef/knife' require 'knife-solo/ssh_command' -require 'knife-solo/config' class Chef class Knife class SoloClean < Knife include KnifeSolo::SshCommand + deps do + require 'knife-solo/tools' + KnifeSolo::SshCommand.load_deps + end + banner "knife solo clean [USER@]HOSTNAME" + option :provisioning_path, + :long => '--provisioning-path path', + :description => 'Where to store kitchen data on the node' + # TODO de-duplicate this option with solo cook + def run - @solo_config = KnifeSolo::Config.new validate! - run_command "rm -rf #{@solo_config.chef_path}" + run_command "rm -rf #{provisioning_path}" end def validate! validate_ssh_options! - @solo_config.validate! + end + + def provisioning_path + # TODO de-duplicate this method with solo cook + KnifeSolo::Tools.config_value(config, :provisioning_path, '~/chef-solo') end end end diff --git a/lib/chef/knife/solo_cook.rb b/lib/chef/knife/solo_cook.rb index 6f30243d..912480c0 100644 --- a/lib/chef/knife/solo_cook.rb +++ b/lib/chef/knife/solo_cook.rb @@ -4,7 +4,6 @@ require 'knife-solo/ssh_command' require 'knife-solo/node_config_command' require 'knife-solo/tools' -require 'knife-solo/config' class Chef class Knife @@ -19,7 +18,9 @@ class SoloCook < Knife deps do require 'chef/cookbook/chefignore' + require 'erubis' require 'pathname' + require 'tempfile' KnifeSolo::SshCommand.load_deps KnifeSolo::NodeConfigCommand.load_deps end @@ -53,9 +54,11 @@ class SoloCook < Knife :long => '--override-runlist', :description => 'Replace current run list with specified items' - def run - @solo_config = KnifeSolo::Config.new + option :provisioning_path, + :long => '--provisioning-path path', + :description => 'Where to store kitchen data on the node' + def run time('Run') do if config[:skip_chef_check] @@ -70,21 +73,45 @@ def run check_chef_version if config[:chef_check] generate_node_config librarian_install if config_value(:librarian, true) - rsync_kitchen - add_patches - add_solo_config unless using_custom_solorb? + sync_kitchen + generate_solorb cook unless config[:sync_only] end end - def_delegators :@solo_config, - :chef_path, - :using_custom_solorb?, - :patch_path - def validate! validate_ssh_options! - @solo_config.validate! + + if File.exist? 'solo.rb' + ui.warn "solo.rb found, but since knife-solo v0.3.0 it is not used any more" + ui.warn "Please read the upgrade instructions: https://github.com/matschaffer/knife-solo/wiki/Upgrading-to-0.3.0" + end + end + + def provisioning_path + # TODO ~ will likely break on cmd.exe based windows sessions + config_value(:provisioning_path, '~/chef-solo') + end + + def sync_kitchen + ui.msg "Uploading the kitchen..." + run_portable_mkdir_p(provisioning_path, '0700') + + cookbook_paths.each_with_index do |path, i| + upload_to_provision_path(path, "/cookbooks-#{i + 1}", 'cookbook_path') + end + upload_to_provision_path(nodes_path, 'nodes') + upload_to_provision_path(:role_path, 'roles') + upload_to_provision_path(:data_bag_path, 'data_bags') + upload_to_provision_path(:encrypted_data_bag_secret, 'data_bag_key') + end + + def cookbook_paths + Array(Chef::Config[:cookbook_path]) + [KnifeSolo.resource('patch_cookbooks').to_s] + end + + def nodes_path + 'nodes' end def chefignore @@ -136,10 +163,10 @@ def librarian_install ui.warn "Librarian-Chef could not be loaded" ui.warn "Please add the librarian gem to your Gemfile or install it manually with `gem install librarian`" else - ui.msg "Installing Librarian cookbooks..." - Librarian::Action::Resolve.new(librarian_env).run - Librarian::Action::Install.new(librarian_env).run - end + ui.msg "Installing Librarian cookbooks..." + Librarian::Action::Resolve.new(librarian_env).run + Librarian::Action::Install.new(librarian_env).run + end end def load_librarian @@ -157,29 +184,43 @@ def librarian_env @librarian_env ||= Librarian::Chef::Environment.new end - def rsync_kitchen - ui.msg "Syncing kitchen..." - time('Rsync kitchen') do - rsync('./', chef_path, '--delete') - end + def generate_solorb + ui.msg "Generating solo config..." + template = Erubis::Eruby.new(KnifeSolo.resource('solo.rb.erb').read) + write(template.result(binding), provisioning_path + '/solo.rb') end - def add_patches - ui.msg "Adding patches..." - run_portable_mkdir_p(patch_path) - Dir[Pathname.new(__FILE__).dirname.join("patches", "*.rb").to_s].each do |patch| - time(patch) do - rsync(patch, patch_path) - end + def upload(src, dest) + rsync(src, dest) + end + + def upload_to_provision_path(src, dest, key_name = 'path') + if src.is_a? Symbol + key_name = src.to_s + src = Chef::Config[src] + end + + if src.nil? + Chef::Log.debug "'#{key_name}' not set" + elsif !File.exist?(src) + ui.warn "Local #{key_name} '#{src}' does not exist" + else + src << '/' if File.directory? src + upload(src, File.join(provisioning_path, dest)) end end - def add_solo_config - ui.msg "Syncing solo config..." - rsync(KnifeSolo.resource('solo.rb'), chef_path) + # TODO probably can get Net::SSH to do this directly + def write(content, dest) + file = Tempfile.new(File.basename(dest)) + file.write(content) + file.close + upload(file.path, dest) + ensure + file.unlink end - def rsync(source_path, target_path, extra_opts = '') + def rsync(source_path, target_path, extra_opts = '--delete') cmd = %Q{rsync -rl #{rsync_permissions} --rsh="ssh #{ssh_args}" #{extra_opts} #{rsync_excludes.collect{ |ignore| "--exclude #{ignore} " }.join} #{adjust_rsync_path_on_client(source_path)} :#{adjust_rsync_path_on_node(target_path)}} ui.msg cmd if debug? system! cmd @@ -200,7 +241,7 @@ def chef_version def cook ui.msg "Running Chef..." - cmd = "sudo chef-solo -c #{chef_path}/solo.rb -j #{chef_path}/#{node_config}" + cmd = "sudo chef-solo -c #{provisioning_path}/solo.rb -j #{provisioning_path}/#{node_config}" cmd << " -l debug" if debug? cmd << " -N #{config[:chef_node_name]}" if config[:chef_node_name] cmd << " -W" if config[:why_run] diff --git a/lib/chef/knife/solo_init.rb b/lib/chef/knife/solo_init.rb index f249c6a4..2c6cc6a0 100644 --- a/lib/chef/knife/solo_init.rb +++ b/lib/chef/knife/solo_init.rb @@ -1,4 +1,5 @@ require 'chef/knife' +require 'fileutils' class Chef class Knife @@ -24,6 +25,7 @@ def run @base = @name_args.first validate! create_kitchen + create_config create_cupboards %w[nodes roles data_bags site-cookbooks cookbooks] librarian_init if config[:librarian] end @@ -52,6 +54,15 @@ def create_kitchen mkdir @base unless @base == '.' end + def create_config + ui.msg "Creating knife.rb in kitchen..." + mkdir_p File.join(@base, '.chef') + knife_rb = File.join(@base, '.chef', 'knife.rb') + unless File.exist?(knife_rb) + cp KnifeSolo.resource('knife.rb'), knife_rb + end + end + def librarian_init ui.msg "Setting up Librarian..." cheffile = File.join(@base, 'Cheffile') diff --git a/lib/knife-solo/config.rb b/lib/knife-solo/config.rb deleted file mode 100644 index 190503fa..00000000 --- a/lib/knife-solo/config.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'chef/config' - -module KnifeSolo - # Encapsulates some logic for checking and extracting - # path configuration from the structure of the - # current kitchen - class Config - def solo_path - Chef::Config.knife[:solo_path] - end - - def chef_path - solo_path || './chef-solo' - end - - def cookbook_path - if using_custom_solorb? - Chef::Config.from_file('solo.rb') - Array(Chef::Config.cookbook_path).first - else - chef_path + '/cookbooks' - end - end - - def patch_path - cookbook_path + "/chef_solo_patches/libraries" - end - - def using_custom_solorb? - File.exist?('solo.rb') - end - - def validate! - raise Error, "You have a solo.rb file, but knife[:solo_path] is not set. You probably need to delete solo.rb unless you've customized it. See https://github.com/matschaffer/knife-solo/wiki/Upgrading-to-0.3.0 for more information." if using_custom_solorb? && solo_path.nil? - end - - class Error < StandardError; end - end -end diff --git a/lib/knife-solo/resources/knife.rb b/lib/knife-solo/resources/knife.rb new file mode 100644 index 00000000..a22053b3 --- /dev/null +++ b/lib/knife-solo/resources/knife.rb @@ -0,0 +1,4 @@ +cookbook_path ["cookbooks", "site-cookbooks"] +role_path "roles" +data_bag_path "data_bags" +#encrypted_data_bag_secret "data_bag_key" diff --git a/lib/knife-solo/resources/patch_cookbooks/chef-solo-search b/lib/knife-solo/resources/patch_cookbooks/chef-solo-search new file mode 160000 index 00000000..5ba022d0 --- /dev/null +++ b/lib/knife-solo/resources/patch_cookbooks/chef-solo-search @@ -0,0 +1 @@ +Subproject commit 5ba022d02c51cafc7b5323c1209fbb7936e4f60b diff --git a/lib/knife-solo/resources/solo.rb b/lib/knife-solo/resources/solo.rb deleted file mode 100644 index ea2b1cfd..00000000 --- a/lib/knife-solo/resources/solo.rb +++ /dev/null @@ -1,6 +0,0 @@ -base = File.expand_path('..', __FILE__) - -data_bag_path base + '/data_bags' -encrypted_data_bag_secret base + '/data_bag_key' -role_path base + '/roles' -cookbook_path [ base + '/site-cookbooks', base + '/cookbooks' ] diff --git a/lib/knife-solo/resources/solo.rb.erb b/lib/knife-solo/resources/solo.rb.erb new file mode 100644 index 00000000..ed1606df --- /dev/null +++ b/lib/knife-solo/resources/solo.rb.erb @@ -0,0 +1,10 @@ +base = File.expand_path('..', __FILE__) + +role_path File.join(base, 'roles') +data_bag_path File.join(base, 'data_bags') +encrypted_data_bag_secret File.join(base, 'data_bag_key') + +cookbook_path [] +<% cookbook_paths.each_with_index do |path, i| -%> +cookbook_path << File.join(base, 'cookbooks-<%= i+1 %>') # <%= path %> +<% end -%> diff --git a/lib/knife-solo/ssh_command.rb b/lib/knife-solo/ssh_command.rb index 88002f7a..33567b7c 100644 --- a/lib/knife-solo/ssh_command.rb +++ b/lib/knife-solo/ssh_command.rb @@ -257,12 +257,13 @@ def run_command(command, options={}) # TODO: # - move this to a dedicated "portability" module? # - use ruby in all cases instead? - def run_portable_mkdir_p(folder) + def run_portable_mkdir_p(folder, mode = nil) if windows_node? # no mkdir -p on windows - fake it - run_command %Q{ruby -e "require 'fileutils'; FileUtils.mkdir_p('#{folder}')"} + run_command %Q{ruby -e "require 'fileutils'; FileUtils.mkdir_p('#{folder}', :mode => #{mode})"} else - run_command "mkdir -p #{folder}" + mode_option = (mode.nil? ? "" : "-m #{mode}") + run_command "mkdir -p #{mode_option} #{folder}" end end diff --git a/test/integration/cases/encrypted_data_bag.rb b/test/integration/cases/encrypted_data_bag.rb index 0056b603..7ec5311a 100644 --- a/test/integration/cases/encrypted_data_bag.rb +++ b/test/integration/cases/encrypted_data_bag.rb @@ -5,6 +5,9 @@ def setup super FileUtils.cp $base_dir.join('support', 'data_bag_key'), 'data_bag_key' FileUtils.cp_r $base_dir.join('support', 'secret_cookbook'), 'cookbooks/secret_cookbook' + File.open('.chef/knife.rb', 'a') do |f| + f.puts 'encrypted_data_bag_secret "data_bag_key"' + end @password = "essential particles busy loud" create_data_bag end diff --git a/test/knife-solo/config_test.rb b/test/knife-solo/config_test.rb deleted file mode 100644 index f5baba6c..00000000 --- a/test/knife-solo/config_test.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'test_helper' -require 'knife-solo/config' - -class KnifeSoloConfigTest < TestCase - def setup - super - @config = KnifeSolo::Config.new - end - - def teardown - super - FileUtils.rm_f 'solo.rb' - end - - def test_uses_cookbook_path_from_solo_rb_if_available - write_file('solo.rb', <<-RUBY) - knife[:solo_path] = "./custom" - cookbook_path ["./custom/path"] - RUBY - assert_equal "./custom/path", @config.cookbook_path - end - - def test_reads_chef_root_path_from_knife_config_or_defaults_to_home - assert_equal './chef-solo', @config.chef_path - Chef::Config.knife[:solo_path] = "/tmp/custom-chef-solo" - assert_equal "/tmp/custom-chef-solo", @config.chef_path - end - - def test_fails_validation_if_user_has_solo_rb_and_no_solo_path - Chef::Config.knife[:solo_path] = nil - write_file('solo.rb', <<-RUBY) - cookbook_path ["custom/path"] - RUBY - assert_raises KnifeSolo::Config::Error do - @config.validate! - end - end -end diff --git a/test/solo_cook_test.rb b/test/solo_cook_test.rb index 9d651ac7..a1c5557e 100644 --- a/test/solo_cook_test.rb +++ b/test/solo_cook_test.rb @@ -165,7 +165,7 @@ def assert_chef_solo_option(cook_option, chef_solo_option) def command(*args) cmd = knife_command(Chef::Knife::SoloCook, *args) cmd.stubs(:check_chef_version) - cmd.stubs(:add_patches) + cmd.stubs(:run_portable_mkdir_p) cmd.stubs(:rsync) cmd.stubs(:stream_command).returns(SuccessfulResult.new) cmd