chef-sandwich 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/NEWS CHANGED
@@ -1,4 +1,13 @@
1
1
  * sandwich NEWS
2
+ ** 0.3.0 (2011-10-17)
3
+
4
+ - Add -e option
5
+ - Add support for cookbooks: sandwich can now load other cookbooks
6
+ - Show chef version in --version output
7
+ - Change default log level from "warn" to "error"
8
+ - Change cookbook file lookup to work relative to sandwich scripts,
9
+ fixes #6
10
+
2
11
  ** 0.2.0 (2011-09-07)
3
12
 
4
13
  - Add support for cookbook_file and template resources
data/Rakefile CHANGED
@@ -1,6 +1,13 @@
1
+ require 'rake/testtask'
2
+
1
3
  namespace :gem do
2
4
  desc 'Build chef-sandwich gem'
3
5
  task :build do
4
6
  sh 'gem build chef-sandwich.gemspec'
5
7
  end
6
8
  end
9
+
10
+ Rake::TestTask.new do |t|
11
+ t.pattern = 'spec/*_spec.rb'
12
+ t.verbose = false
13
+ end
data/lib/sandwich/cli.rb CHANGED
@@ -10,7 +10,9 @@ module Sandwich
10
10
  @options = {}
11
11
 
12
12
  # default log level
13
- @options[:log_level] = :warn
13
+ @options[:log_level] = :error
14
+
15
+ @options[:command] = []
14
16
 
15
17
  @optparse = OptionParser.new do |opts|
16
18
  opts.banner = 'Usage: sandwich [options] [sandwichfile [arguments]]'
@@ -18,7 +20,8 @@ module Sandwich
18
20
  opts.on_tail('-v',
19
21
  '--version',
20
22
  'Show sandwich version') do
21
- puts "sandwich: #{Sandwich::Version}"
23
+ puts "sandwich: #{Sandwich::VERSION}"
24
+ puts "chef: #{Chef::VERSION}"
22
25
  exit
23
26
  end
24
27
 
@@ -34,6 +37,12 @@ module Sandwich
34
37
  'Set the log level (debug, info, warn, error, fatal)') do |l|
35
38
  @options[:log_level] = l.to_sym
36
39
  end
40
+
41
+ opts.on('-e',
42
+ '--command COMMAND',
43
+ 'Execute command as a sandwich script') do |c|
44
+ @options[:command] << c
45
+ end
37
46
  end
38
47
  end
39
48
 
@@ -44,21 +53,27 @@ module Sandwich
44
53
  def run(argv)
45
54
  unparsed_arguments = @optparse.order!(argv)
46
55
 
47
- # use first argument as sandwich script filename...
48
- recipe_filename = unparsed_arguments.shift
49
-
50
- # ...check for stdin...
51
- if recipe_filename.nil? || recipe_filename == '-'
52
- recipe_filename = '<STDIN>'
53
- recipe_file = STDIN.read
56
+ # check for -e commands
57
+ if @options[:command].any?
58
+ recipe_filename = '<COMMAND>'
59
+ recipe = @options[:command].join("\n")
54
60
  else
55
- recipe_file = File.read(recipe_filename)
61
+ # use first argument as sandwich script filename...
62
+ recipe_filename = unparsed_arguments.shift
63
+
64
+ # check for stdin
65
+ if recipe_filename.nil? || recipe_filename == '-'
66
+ recipe_filename = '<STDIN>'
67
+ recipe = STDIN.read
68
+ else
69
+ recipe = nil
70
+ end
56
71
  end
57
72
 
58
- # ...and pass remaining arguments on to script
73
+ # pass remaining arguments on to script
59
74
  ARGV.replace(unparsed_arguments)
60
- runner = Sandwich::Runner.new(recipe_file, recipe_filename)
61
75
 
76
+ runner = Sandwich::Runner.new(recipe_filename, recipe)
62
77
  runner.run(@options[:log_level])
63
78
  end
64
79
  end
@@ -0,0 +1,82 @@
1
+ require 'sandwich/cookbook_version'
2
+ require 'chef'
3
+
4
+ module Sandwich
5
+ # Chef::Client extended to inject a Sandwich cookbook into the
6
+ # client's run context
7
+ class Client < Chef::Client
8
+ # Create a new instance of Client.
9
+ #
10
+ # File specified by +recipe_filename+ is only read if no
11
+ # +recipe_string+ is supplied, otherwise +recipe_string+ is used
12
+ # as a recipe and +recipe_filename+ is only used in log messages.
13
+ #
14
+ # @param [String] recipe_filename the recipe filename
15
+ # @param [String] recipe_string the recipe definition
16
+ def initialize(recipe_filename, recipe_string = nil)
17
+ Chef::Config[:solo] = true
18
+ super()
19
+
20
+ if recipe_string
21
+ @sandwich_basedir = Dir.getwd
22
+ else
23
+ @sandwich_basedir = File.expand_path(File.dirname(recipe_filename))
24
+ recipe_string = File.read(recipe_filename)
25
+ end
26
+
27
+ add_cookbook_dir(@sandwich_basedir)
28
+
29
+ @sandwich_cookbook_name = unique_cookbook_name
30
+ @sandwich_recipe = recipe_string
31
+ @sandwich_filename = recipe_filename
32
+ end
33
+
34
+ # Silently build a new node object for this client
35
+ #
36
+ # @return [Chef::Node] the created node object
37
+ def build_node
38
+ # silence build_node from super class
39
+ silence_chef { super }
40
+ end
41
+
42
+ # Set up the client's run context, inject Sandwich cookbook into
43
+ # the created run context
44
+ #
45
+ # @return [Chef::RunContext] the created run context
46
+ def setup_run_context
47
+ run_context = super
48
+ cookbook = Sandwich::CookbookVersion.new(@sandwich_cookbook_name,
49
+ @sandwich_basedir)
50
+ run_context.cookbook_collection[@sandwich_cookbook_name] = cookbook
51
+ recipe = Chef::Recipe.new(@sandwich_cookbook_name, nil, run_context)
52
+ recipe.from_string(@sandwich_recipe, @sandwich_filename)
53
+ run_context
54
+ end
55
+
56
+ # Add a cookbook directory to the front of the client's cookbook
57
+ # search path.
58
+ #
59
+ # @param [String] cookbook_dir the cookbook directory to add
60
+ # @return [Array] the new cookbook search path
61
+ def add_cookbook_dir(cookbook_dir)
62
+ Chef::Config[:cookbook_path].unshift(cookbook_dir)
63
+ end
64
+
65
+ private
66
+ def silence_chef
67
+ log_level = Chef::Log.level
68
+ Chef::Log.level = :fatal
69
+ yield
70
+ ensure
71
+ Chef::Log.level = log_level
72
+ end
73
+
74
+ # monkey patch: don't check for empty cookbook paths
75
+ def assert_cookbook_path_not_empty(run_context); end
76
+
77
+ def unique_cookbook_name
78
+ # use uuid in sandwich cookbook name to avoid name collisions
79
+ "sandwich_#{UUIDTools::UUID.random_create}"
80
+ end
81
+ end
82
+ end
@@ -1,21 +1,31 @@
1
1
  require 'chef/cookbook_version'
2
2
 
3
- # Chef::CookbookVersion, monkey patched to use simpler file source
4
- # paths (always uses local files instead of manifest records)
5
- class Chef::CookbookVersion
6
- # Determines the absolute source filename on disk for various file
7
- # resources from their relative path
8
- #
9
- # @param [Chef::Node] node the node object, ignored
10
- # @param [Symbol] segment the segment of the current resource, ignored
11
- # @param [String] filename the source file path
12
- # @param [String] current_filepath the target file path, ignored
13
- # @return [String] the preferred source filename
14
- def preferred_filename_on_disk_location(node,
15
- segment,
16
- filename,
17
- current_filepath=nil)
18
- # keep absolute paths, convert relative paths into absolute paths
19
- filename.start_with?('/') ? filename : File.join(Dir.getwd, filename)
3
+ module Sandwich
4
+ # Chef::CookbookVersion, extended to use simpler file source paths
5
+ # (always uses local files relative to @basedir instead of manifest
6
+ # records)
7
+ class CookbookVersion < Chef::CookbookVersion
8
+ # @param [String] name the cookbook's name
9
+ # @param [String basedir the cookbook's basedir
10
+ def initialize(name, basedir)
11
+ super(name)
12
+ @basedir = basedir
13
+ end
14
+
15
+ # Determines the absolute source filename on disk for various file
16
+ # resources from their relative path
17
+ #
18
+ # @param [Chef::Node] node the node object, ignored
19
+ # @param [Symbol] segment the segment of the current resource, ignored
20
+ # @param [String] filename the source file path
21
+ # @param [String] current_filepath the target file path, ignored
22
+ # @return [String] the preferred source filename
23
+ def preferred_filename_on_disk_location(node,
24
+ segment,
25
+ filename,
26
+ current_filepath=nil)
27
+ # keep absolute paths, convert relative paths into absolute paths
28
+ filename.start_with?('/') ? filename : File.join(@basedir, filename)
29
+ end
20
30
  end
21
31
  end
@@ -1,19 +1,21 @@
1
- require 'chef'
1
+ require 'chef/config'
2
2
  require 'sandwich/recipe'
3
- require 'sandwich/cookbook_version'
3
+ require 'sandwich/client'
4
4
 
5
5
  module Sandwich
6
6
  # This class constructs a {Chef::Recipe} from a recipe string and
7
- # applies it with Chef standalone mode
7
+ # applies it with Chef standalone mode.
8
8
  class Runner
9
+ # Create a new instance of Runner.
10
+ #
11
+ # File specified by +recipe_filename+ is only read if no
12
+ # +recipe_string+ is supplied, otherwise +recipe_string+ is used
13
+ # as a recipe and +recipe_filename+ is only used in log messages.
14
+ #
15
+ # @param [String] recipe_filename the recipe filename
9
16
  # @param [String] recipe_string the recipe definition
10
- def initialize(recipe_string, filename)
11
- @client = solo_client
12
- cookbook_name = 'sandwich'
13
- cookbook_collection = single_cookbook_collection(cookbook_name)
14
- @run_context = Chef::RunContext.new(@client.node, cookbook_collection)
15
- @recipe = Chef::Recipe.new(cookbook_name, nil, @run_context)
16
- @recipe.from_string(recipe_string, filename)
17
+ def initialize(recipe_filename, recipe_string = nil)
18
+ @client = Sandwich::Client.new(recipe_filename, recipe_string)
17
19
  end
18
20
 
19
21
  # Run Chef in standalone mode, apply recipe
@@ -25,17 +27,10 @@ module Sandwich
25
27
  # @return [void]
26
28
  def run(log_level = :warn)
27
29
  configure_chef(log_level)
28
- Chef::Runner.new(@run_context).converge
30
+ @client.run
29
31
  end
30
32
 
31
33
  private
32
- def solo_client
33
- Chef::Config[:solo] = true
34
- client = Chef::Client.new
35
- client.run_ohai
36
- client.build_node
37
- end
38
-
39
34
  def configure_chef(log_level)
40
35
  Chef::Log.level = log_level
41
36
 
@@ -46,11 +41,5 @@ module Sandwich
46
41
  Chef::Config[:file_backup_path] = File.join(local_cache, 'backup')
47
42
  end
48
43
  end
49
-
50
- # create a cookbook collection containing a single empty cookbook
51
- def single_cookbook_collection(cookbook_name)
52
- cookbook = Chef::CookbookVersion.new(cookbook_name)
53
- Chef::CookbookCollection.new({ cookbook_name => cookbook })
54
- end
55
44
  end
56
45
  end
@@ -1,4 +1,4 @@
1
1
  module Sandwich
2
2
  # the current version of sandwich
3
- Version = '0.2.0'
3
+ VERSION = '0.3.0'
4
4
  end
@@ -0,0 +1,59 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__)
2
+ require 'spec_helper'
3
+
4
+ describe Sandwich::Runner do
5
+ after(:each) do
6
+ FakeFS::FileSystem.clear
7
+ end
8
+
9
+ describe Chef::Resource::File do
10
+ it 'should create files' do
11
+ with_fakefs do
12
+ filename = '/foo'
13
+ content = 'hello world'
14
+ recipe = %Q(file '#{filename}' do content '#{content}';end)
15
+ run_recipe(recipe)
16
+ file = File.read(filename)
17
+ file.must_equal content
18
+ end
19
+ end
20
+
21
+ it 'should throw exceptions for files in missing directories' do
22
+ with_fakefs do
23
+ filename = '/i/am/not/here'
24
+ content = 'hello world'
25
+ recipe = %Q(file '#{filename}' do content '#{content}';end)
26
+ run = Proc.new { run_recipe recipe }
27
+ run.must_raise(Errno::ENOENT,
28
+ Chef::Exceptions::EnclosingDirectoryDoesNotExist)
29
+ end
30
+ end
31
+ end
32
+
33
+ describe Chef::Resource::CookbookFile do
34
+ it 'should create cookbook files' do
35
+ with_fakefs do
36
+ source = '/source'
37
+ target = '/target'
38
+ content = 'hello world'
39
+ recipe = %Q(cookbook_file '#{target}' do source '#{source}';end)
40
+ # create source for cookbook file
41
+ File.open(source, 'w') { |f| f.write(content) }
42
+ run_recipe(recipe)
43
+ File.read(source).must_equal File.read(target)
44
+ end
45
+ end
46
+ end
47
+
48
+ describe Chef::Resource::Directory do
49
+ it 'should create directories' do
50
+ with_fakefs do
51
+ dir = '/foo'
52
+ recipe = %Q(directory '#{dir}')
53
+ assert !Dir.exists?(dir)
54
+ run_recipe(recipe)
55
+ assert Dir.exists?(dir)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,47 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'fileutils'
4
+ require 'fakefs/safe'
5
+ require 'sandwich/runner'
6
+ require 'chef'
7
+
8
+ # Ohai::System, monkey patched to return static values
9
+ class Ohai::System
10
+ def all_plugins
11
+ @data = Mash.new({ :hostname => 'archie',
12
+ :platform => 'ubuntu',
13
+ :fqdn => 'archie.example.com',
14
+ :platform_version => '11.04',
15
+ :os_version => '2.6.38-10-generic' })
16
+ end
17
+ end
18
+
19
+ # monkey patch for https://github.com/defunkt/fakefs/issues/96
20
+ class FakeFS::Dir
21
+ def self.mkdir(path, integer = 0)
22
+ FileUtils.mkdir(path)
23
+ end
24
+ end
25
+
26
+ def runner_from_recipe(recipe)
27
+ Sandwich::Runner.new('<SPEC_HELPER>', recipe)
28
+ end
29
+
30
+ def run_recipe(recipe)
31
+ runner_from_recipe(recipe).run(:fatal)
32
+ end
33
+
34
+ def with_fakefs
35
+ FakeFS.activate!
36
+ setup_standard_dirs
37
+ yield
38
+ ensure
39
+ FakeFS.deactivate!
40
+ end
41
+
42
+ def setup_standard_dirs
43
+ FileUtils.mkdir_p '/tmp'
44
+ end
45
+
46
+ # make sure Chef 0.10 exceptions are available when using older Chef versions
47
+ class Chef::Exceptions::EnclosingDirectoryDoesNotExist; end
metadata CHANGED
@@ -1,13 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: chef-sandwich
3
3
  version: !ruby/object:Gem::Version
4
- hash: 23
5
- prerelease: false
6
- segments:
7
- - 0
8
- - 2
9
- - 0
10
- version: 0.2.0
4
+ prerelease:
5
+ version: 0.3.0
11
6
  platform: ruby
12
7
  authors:
13
8
  - Sebastian Boehm
@@ -15,7 +10,7 @@ autorequire:
15
10
  bindir: bin
16
11
  cert_chain: []
17
12
 
18
- date: 2011-09-07 00:00:00 +02:00
13
+ date: 2011-10-17 00:00:00 +02:00
19
14
  default_executable:
20
15
  dependencies:
21
16
  - !ruby/object:Gem::Dependency
@@ -26,13 +21,53 @@ dependencies:
26
21
  requirements:
27
22
  - - ">="
28
23
  - !ruby/object:Gem::Version
29
- hash: 25
30
- segments:
31
- - 0
32
- - 9
33
24
  version: "0.9"
34
25
  type: :runtime
35
26
  version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: uuidtools
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: minitest
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id003
49
+ - !ruby/object:Gem::Dependency
50
+ name: fakefs
51
+ prerelease: false
52
+ requirement: &id004 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ type: :development
59
+ version_requirements: *id004
60
+ - !ruby/object:Gem::Dependency
61
+ name: rake
62
+ prerelease: false
63
+ requirement: &id005 !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ~>
67
+ - !ruby/object:Gem::Version
68
+ version: 0.8.7
69
+ type: :development
70
+ version_requirements: *id005
36
71
  description: |
37
72
  Sandwich lets you apply Chef recipes to your system without having to
38
73
  worry about cookbooks or configuration.
@@ -50,13 +85,16 @@ files:
50
85
  - LICENSE
51
86
  - NEWS
52
87
  - bin/sandwich
53
- - lib/sandwich.rb
54
- - lib/sandwich/version.rb
55
88
  - lib/sandwich/cli.rb
56
- - lib/sandwich/recipe.rb
89
+ - lib/sandwich/client.rb
57
90
  - lib/sandwich/cookbook_version.rb
91
+ - lib/sandwich/recipe.rb
58
92
  - lib/sandwich/runner.rb
59
- has_rdoc: yard
93
+ - lib/sandwich/version.rb
94
+ - lib/sandwich.rb
95
+ - spec/runner_spec.rb
96
+ - spec/spec_helper.rb
97
+ has_rdoc: true
60
98
  homepage: https://github.com/sometimesfood/sandwich
61
99
  licenses: []
62
100
 
@@ -70,23 +108,17 @@ required_ruby_version: !ruby/object:Gem::Requirement
70
108
  requirements:
71
109
  - - ">="
72
110
  - !ruby/object:Gem::Version
73
- hash: 3
74
- segments:
75
- - 0
76
111
  version: "0"
77
112
  required_rubygems_version: !ruby/object:Gem::Requirement
78
113
  none: false
79
114
  requirements:
80
115
  - - ">="
81
116
  - !ruby/object:Gem::Version
82
- hash: 3
83
- segments:
84
- - 0
85
117
  version: "0"
86
118
  requirements: []
87
119
 
88
120
  rubyforge_project:
89
- rubygems_version: 1.3.7
121
+ rubygems_version: 1.6.2
90
122
  signing_key:
91
123
  specification_version: 3
92
124
  summary: The easiest way to get started as a chef