chefdepartie 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ffda5a4e9b1258d25ba59354dbdb73148af675b4
4
+ data.tar.gz: ad14a0b279ec6541be45be51ef4fbbbd350c6367
5
+ SHA512:
6
+ metadata.gz: ff88fbed52f7d75d518a8b1924bf9c4b179bd0d67ee110dcc778d21040658f3c5d5b4663df905c05c883d260ad04eb207c341670ba0a535b9770b49966200323
7
+ data.tar.gz: abf7fa5b9c495221ae3425e7150716ef8f93120eb7a0164772f8294d270f9f972066143798892a481dd25fa79ec0eb72cae2eb3be56acee4da992f104b177325
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ cookbooks
2
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :test do
6
+ gem 'simplecov', '=0.10.0'
7
+ end
8
+
data/Gemfile.lock ADDED
@@ -0,0 +1,142 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ chefdepartie (0.0.3)
5
+ chef (~> 12.3.0)
6
+ chef-zero (~> 4.2.2)
7
+ librarian-chef (~> 0.0.4)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ builder (3.2.2)
13
+ chef (12.3.0)
14
+ chef-zero (~> 4.1)
15
+ diff-lcs (~> 1.2, >= 1.2.4)
16
+ erubis (~> 2.7)
17
+ ffi-yajl (>= 1.2, < 3.0)
18
+ highline (~> 1.6, >= 1.6.9)
19
+ mixlib-authentication (~> 1.3)
20
+ mixlib-cli (~> 1.4)
21
+ mixlib-config (~> 2.0)
22
+ mixlib-log (~> 1.3)
23
+ mixlib-shellout (>= 2.0.0.rc.0, < 3.0)
24
+ net-ssh (~> 2.6)
25
+ net-ssh-multi (~> 1.1)
26
+ ohai (~> 8.0)
27
+ plist (~> 3.1.0)
28
+ pry (~> 0.9)
29
+ rspec-core (~> 3.2)
30
+ rspec-expectations (~> 3.2)
31
+ rspec-mocks (~> 3.2)
32
+ rspec_junit_formatter (~> 0.2.0)
33
+ serverspec (~> 2.7)
34
+ specinfra (~> 2.10)
35
+ chef-zero (4.2.2)
36
+ ffi-yajl (>= 1.1, < 3.0)
37
+ hashie (~> 2.0)
38
+ mixlib-log (~> 1.3)
39
+ rack
40
+ uuidtools (~> 2.1)
41
+ coderay (1.1.0)
42
+ diff-lcs (1.2.5)
43
+ docile (1.1.5)
44
+ erubis (2.7.0)
45
+ ffi (1.9.8)
46
+ ffi-yajl (2.2.0)
47
+ libyajl2 (~> 1.2)
48
+ hashie (2.1.2)
49
+ highline (1.7.2)
50
+ ipaddress (0.8.0)
51
+ json (1.8.2)
52
+ librarian (0.1.2)
53
+ highline
54
+ thor (~> 0.15)
55
+ librarian-chef (0.0.4)
56
+ chef (>= 0.10)
57
+ librarian (~> 0.1.0)
58
+ minitar (>= 0.5.2)
59
+ libyajl2 (1.2.0)
60
+ method_source (0.8.2)
61
+ mime-types (2.6.1)
62
+ minitar (0.5.4)
63
+ minitest (5.6.1)
64
+ mixlib-authentication (1.3.0)
65
+ mixlib-log
66
+ mixlib-cli (1.5.0)
67
+ mixlib-config (2.2.1)
68
+ mixlib-log (1.6.0)
69
+ mixlib-shellout (2.1.0)
70
+ multi_json (1.11.1)
71
+ net-scp (1.2.1)
72
+ net-ssh (>= 2.6.5)
73
+ net-ssh (2.9.2)
74
+ net-ssh-gateway (1.2.0)
75
+ net-ssh (>= 2.6.5)
76
+ net-ssh-multi (1.2.1)
77
+ net-ssh (>= 2.6.5)
78
+ net-ssh-gateway (>= 1.2.0)
79
+ ohai (8.4.0)
80
+ ffi (~> 1.9)
81
+ ffi-yajl (>= 1.1, < 3.0)
82
+ ipaddress
83
+ mime-types (~> 2.0)
84
+ mixlib-cli
85
+ mixlib-config (~> 2.0)
86
+ mixlib-log
87
+ mixlib-shellout (~> 2.0)
88
+ rake (~> 10.1)
89
+ systemu (~> 2.6.4)
90
+ wmi-lite (~> 1.0)
91
+ plist (3.1.0)
92
+ pry (0.10.1)
93
+ coderay (~> 1.1.0)
94
+ method_source (~> 0.8.1)
95
+ slop (~> 3.4)
96
+ rack (1.6.2)
97
+ rake (10.4.2)
98
+ rspec (3.3.0)
99
+ rspec-core (~> 3.3.0)
100
+ rspec-expectations (~> 3.3.0)
101
+ rspec-mocks (~> 3.3.0)
102
+ rspec-core (3.3.0)
103
+ rspec-support (~> 3.3.0)
104
+ rspec-expectations (3.3.0)
105
+ diff-lcs (>= 1.2.0, < 2.0)
106
+ rspec-support (~> 3.3.0)
107
+ rspec-its (1.2.0)
108
+ rspec-core (>= 3.0.0)
109
+ rspec-expectations (>= 3.0.0)
110
+ rspec-mocks (3.3.0)
111
+ diff-lcs (>= 1.2.0, < 2.0)
112
+ rspec-support (~> 3.3.0)
113
+ rspec-support (3.3.0)
114
+ rspec_junit_formatter (0.2.3)
115
+ builder (< 4)
116
+ rspec-core (>= 2, < 4, != 2.12.0)
117
+ serverspec (2.18.0)
118
+ multi_json
119
+ rspec (~> 3.0)
120
+ rspec-its
121
+ specinfra (~> 2.35)
122
+ simplecov (0.10.0)
123
+ docile (~> 1.1.0)
124
+ json (~> 1.8)
125
+ simplecov-html (~> 0.10.0)
126
+ simplecov-html (0.10.0)
127
+ slop (3.6.0)
128
+ specinfra (2.35.1)
129
+ net-scp
130
+ net-ssh
131
+ systemu (2.6.5)
132
+ thor (0.19.1)
133
+ uuidtools (2.1.5)
134
+ wmi-lite (1.0.0)
135
+
136
+ PLATFORMS
137
+ ruby
138
+
139
+ DEPENDENCIES
140
+ chefdepartie!
141
+ minitest (~> 5.6)
142
+ simplecov (= 0.10.0)
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # A quick little helper
2
+
3
+ In a kitchen, a chef de partie is a line cook who helps prepare the ingredients the real chef will use.
4
+
5
+ Chefdepartie is a wrapper around a chef-zero server, allowing you to easily upload all of your:
6
+
7
+ * Cookbooks (site)
8
+ * Librarian cookbooks
9
+ * Roles
10
+ * Data bags
11
+
12
+ This allows you to test out all of your cookbooks together without putting them on your actual chef server.
13
+
14
+ ## Chefdepartie vs chef-solo
15
+
16
+ Since chef-zero provides the same interface as **a real chef server**, you can actually use knife with it!
17
+
18
+ This gives you a full chef sandbox, and can be useful for things like CI or packer image provisioning.
19
+
20
+ * View and edit data bags, without an internet connection
21
+ * Bootstrap test nodes (vagrant images, cloud servers, etc)
22
+
23
+ ## Limitations
24
+
25
+ Since chefdepartie is just a wrapper around chef-zero, it runs entirely in memory and has no persistence. This makes it a useful sandbox, but it shouldn't be relied on for anything non-ephemeral.
26
+
27
+ chefdepartie really doesn't work with environments. Everything is assumed to be on the default environment.
28
+
29
+ # Configuration
30
+
31
+ create a normal chef config file, but you only need to specify the following values:
32
+
33
+ ```
34
+ chef_server_url 'http://localhost:4000'
35
+ client_key 'path_to_any_pemfile'
36
+ encrypted_data_bag_secret 'path_to_your_real_databag_secret'
37
+ cookbook_path 'path_to_your_cookbooks'
38
+ node_name 'any_name'
39
+ ```
40
+
41
+ Then, provide this file to chefdepartie as an environment variable (yeah, this needs some tweaking)
42
+
43
+
44
+ # Invocation
45
+
46
+ Currently a little rough around the edges. You specify the chef config file to use, and then just run chefdepartie.
47
+
48
+ ```
49
+ CHEFDEPARTIE_CONFIG=~/workspace/shopify/chefdepartie/config.rb be ruby lib/chefdepartie.rb
50
+ ```
51
+
52
+ # To do:
53
+
54
+ * Wrap launching server with something like thor
55
+ * Tests to ensure all uploads are working as expected
56
+ * CI for tests
@@ -0,0 +1,25 @@
1
+ lib = File.expand_path('../lib', __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'chefdepartie/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'chefdepartie'
7
+ spec.version = Chefdepartie::VERSION
8
+ spec.summary = 'chefdepartie helps you test you cookbooks locally'
9
+ spec.description = 'chefdepartie uses chef-zero to provide a local, testing chef server'
10
+ spec.authors = ['Dale Hamel']
11
+ spec.email = 'dale.hamel@srvthe.net'
12
+ spec.files = Dir['lib/**/*']
13
+ spec.homepage = 'https://rubygems.org/gems/chefdepartie'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency 'chef', '~> 12.3.0'
22
+ spec.add_runtime_dependency 'chef-zero', '~> 4.2.2'
23
+ spec.add_runtime_dependency 'librarian-chef', '~> 0.0.4'
24
+ spec.add_development_dependency 'minitest', '~> 5.6'
25
+ end
data/config.rb ADDED
@@ -0,0 +1,5 @@
1
+ chef_server_url 'http://localhost:4000'
2
+ client_key '/home/dale.hamel/.chef/configs/shopify/client.pem'
3
+ encrypted_data_bag_secret '/home/dale.hamel/.chef/configs/shopify/encrypted_data_bag_secret'
4
+ cookbook_path '/home/dale.hamel/workspace/shopify/cookbooks/cookbooks'
5
+ node_name 'foo'
@@ -0,0 +1,72 @@
1
+ require 'chef'
2
+ require 'chef/cookbook/metadata'
3
+ require 'chef/cookbook_uploader'
4
+ require 'librarian'
5
+ require 'librarian/chef'
6
+
7
+ module Chefdepartie
8
+ module Cookbooks
9
+
10
+ def self.upload_all
11
+ cookbooks = File.dirname(Chef::Config[:cookbook_path])
12
+ Dir.chdir(cookbooks) do
13
+ puts "Uploading librarian cookbooks"
14
+ upload_cheffile
15
+
16
+ puts "Uploading site cookbooks"
17
+ books = Dir["cookbooks/*"]
18
+ upload_site_cookbooks(books)
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def self.upload_cookbooks(path, books)
25
+ loader = Chef::CookbookLoader.new(path)
26
+
27
+ books = books.collect do |name|
28
+ status = :new
29
+ cookbook = loader.load_cookbook(name)
30
+ raise "could not load cookbook #{name} " if cookbook.nil?
31
+ cookbook
32
+ end.compact
33
+
34
+ rest = Chef::REST.new(Chef::Config[:chef_server_url])
35
+
36
+ begin
37
+ Chef::CookbookUploader.new(books, {force: true, rest: rest}).upload_cookbooks
38
+ rescue SystemExit => e
39
+ raise "Cookbook upload exited with status #{e.status}"
40
+ end
41
+
42
+ books.map(&:name).map(&:to_s)
43
+ end
44
+
45
+ def self.upload_site_cookbooks(books)
46
+ paths = {}
47
+ books.each do |book|
48
+ path, book = book.split('/', 2)
49
+ paths[path] ||= []
50
+ paths[path] << book
51
+ end
52
+
53
+ paths.each do |path, books|
54
+ upload_cookbooks(path, books)
55
+ end
56
+
57
+ paths.values.flatten.uniq.sort
58
+ end
59
+
60
+ def self.upload_cheffile
61
+ unless ENV['NO_LIBRARIAN']
62
+ system("librarian-chef install")
63
+ upload_cookbooks("tmp/librarian/cookbooks", cheffile_cookbooks.map(&:name))
64
+ end
65
+ end
66
+
67
+ def self.cheffile_cookbooks
68
+ librarian = ::Librarian::Chef.environment_class.new
69
+ librarian.lock.manifests
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,103 @@
1
+ require 'chef'
2
+
3
+ # We monkeypatch chefzero because it does something really stupid with databags, not sure why.
4
+ # It prevents them from being mashes client side. This allows databags to by serialized
5
+ # So that chef-client will load it as a mash instead of a hash
6
+ # The change here is we comment out:
7
+ #
8
+ # data_bag_item = data_bag_item['raw_data']
9
+
10
+ module ChefZero
11
+ module ChefData
12
+ class DataNormalizer
13
+ def self.normalize_data_bag_item(data_bag_item, data_bag_name, id, method)
14
+ if method == 'DELETE'
15
+ # TODO SERIOUSLY, WHO DOES THIS MANY EXCEPTIONS IN THEIR INTERFACE
16
+ if !(data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data'])
17
+ data_bag_item['id'] ||= id
18
+ data_bag_item = { 'raw_data' => data_bag_item }
19
+ data_bag_item['chef_type'] ||= 'data_bag_item'
20
+ data_bag_item['json_class'] ||= 'Chef::DataBagItem'
21
+ data_bag_item['data_bag'] ||= data_bag_name
22
+ data_bag_item['name'] ||= "data_bag_item_#{data_bag_name}_#{id}"
23
+ end
24
+ else
25
+ # If it's not already wrapped with raw_data, wrap it.
26
+ if data_bag_item['json_class'] == 'Chef::DataBagItem' && data_bag_item['raw_data']
27
+ #data_bag_item = data_bag_item['raw_data']
28
+ end
29
+ # Argh. We don't do this on GET, but we do on PUT and POST????
30
+ if %w(PUT POST).include?(method)
31
+ data_bag_item['chef_type'] ||= 'data_bag_item'
32
+ data_bag_item['data_bag'] ||= data_bag_name
33
+ end
34
+ data_bag_item['id'] ||= id
35
+ end
36
+ data_bag_item
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module Chefdepartie
43
+ module Databags
44
+ def self.upload_all
45
+ puts "Uploading databags"
46
+ cookbooks = File.dirname(Chef::Config[:cookbook_path])
47
+ bags = Dir[File.join(cookbooks,'data_bags','/*')]
48
+ upload_all_data_bags(bags)
49
+ end
50
+
51
+ private
52
+
53
+ def self.upload_data_bag_items(items)
54
+ current_bags = Chef::DataBag.list.keys.to_set
55
+
56
+ items.each do |item|
57
+ bag_name = item.data_bag
58
+ unless current_bags.include?(bag_name)
59
+ bag = Chef::DataBag.new
60
+ bag.name(bag_name)
61
+ bag.create
62
+ puts "Created databag #{bag_name.inspect}."
63
+ current_bags << bag_name
64
+ end
65
+
66
+ item.save
67
+ puts "Uploaded databag #{bag_name.inspect}, item #{item.id.inspect}."
68
+ end
69
+ end
70
+
71
+ # Takes a list of data bag names, and uploads all data bag items from within that bag.
72
+ def self.upload_all_data_bags(data_bags)
73
+ items = []
74
+ secret = Chef::EncryptedDataBagItem.load_secret(Chef::Config[:encrypted_data_bag_secret])
75
+
76
+ data_bags.each do |data_bag|
77
+ bag_name = File.basename(data_bag)
78
+ files = Dir.glob(File.join(data_bag,"*.json")).flatten
79
+
80
+ files.each do |item_file|
81
+ raw_data = Chef::JSONCompat.from_json(IO.read(item_file))
82
+
83
+ item = Chef::DataBagItem.new
84
+ item.data_bag bag_name
85
+ if is_encrypted_data_bag?(raw_data)
86
+ item.raw_data = Chef::EncryptedDataBagItem.new(raw_data, secret).to_hash
87
+ else
88
+ item.raw_data = raw_data
89
+ end
90
+ items << item
91
+ end
92
+ end
93
+
94
+ upload_data_bag_items(items)
95
+ end
96
+
97
+ def self.is_encrypted_data_bag?(raw_data)
98
+ # TODO: use Chef::EncryptedDataBagItem::CheckEncrypted when we move to Chef 12
99
+ first_sub_item = Array(raw_data.find { |k, v| k != 'id' }).last
100
+ !!(first_sub_item.kind_of?(Hash) && first_sub_item['encrypted_data'] && first_sub_item['cipher'])
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,44 @@
1
+ require 'chef'
2
+ require 'chef/role'
3
+
4
+ module Chefdepartie
5
+ module Roles
6
+
7
+ def self.upload_all
8
+ puts "Uploading roles"
9
+ cookbooks = File.dirname(Chef::Config[:cookbook_path])
10
+ roles = []
11
+ Find.find(File.join(cookbooks,'roles')){ |f| roles << f if f =~ /\.rb$/}
12
+ upload_site_roles(roles)
13
+ end
14
+
15
+ private
16
+
17
+ def self.upload_site_roles(files)
18
+ roles = {}
19
+ files.each do |f|
20
+ role = role_from_file(f)
21
+ roles[role.name] = role # original name as hash key
22
+ role.name(role.name)
23
+ end
24
+
25
+ upload_roles(roles.values)
26
+
27
+ roles
28
+ end
29
+
30
+ def self.role_from_file(file)
31
+ role = Chef::Role.new
32
+ puts file
33
+ role.from_file(file)
34
+ role
35
+ end
36
+
37
+ def self.upload_roles(roles)
38
+ roles.each do |role|
39
+ role.save
40
+ puts "Uploaded #{role.name.inspect} role."
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ require 'chef_zero/server'
2
+ require 'uri/generic'
3
+
4
+ def start_server
5
+ port = URI(Chef::Config[:chef_server_url]).port
6
+ server = ChefZero::Server.new(host: '0.0.0.0', port: port)
7
+ Thread.new do
8
+ server.start#_background
9
+ end
10
+ end
11
+
@@ -0,0 +1,3 @@
1
+ module Chefdepartie
2
+ VERSION='0.0.3'
3
+ end
@@ -0,0 +1,35 @@
1
+ require 'chef'
2
+
3
+ require_relative 'chefdepartie/server'
4
+ require_relative 'chefdepartie/role'
5
+ require_relative 'chefdepartie/cookbook'
6
+ require_relative 'chefdepartie/databag'
7
+
8
+ module Chefdepartie
9
+ def self.run(**kwargs)
10
+
11
+ # Load the configuration
12
+ config = kwargs[:config]
13
+ Chef::Config.from_file(config)
14
+
15
+ # Start the chef-zero server
16
+ server_thread = start_server
17
+
18
+ # Upload everything
19
+ upload_all
20
+
21
+ # Now that everything has been uploaded, we'll join the server thread
22
+ puts "Ready"
23
+ server_thread.join
24
+ end
25
+
26
+ private
27
+
28
+ def self.upload_all
29
+ Chefdepartie::Roles.upload_all
30
+ Chefdepartie::Databags.upload_all
31
+ Chefdepartie::Cookbooks.upload_all
32
+ end
33
+ end
34
+
35
+ Chefdepartie.run(config: ENV['CHEFDEPARTIE_CONFIG']) # FIXME: use something better than an env var to get the config
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: chefdepartie
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Dale Hamel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-07-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: chef
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 12.3.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 12.3.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: chef-zero
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 4.2.2
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 4.2.2
41
+ - !ruby/object:Gem::Dependency
42
+ name: librarian-chef
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.0.4
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.0.4
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.6'
69
+ description: chefdepartie uses chef-zero to provide a local, testing chef server
70
+ email: dale.hamel@srvthe.net
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".gitignore"
76
+ - Gemfile
77
+ - Gemfile.lock
78
+ - README.md
79
+ - chefdepartie.gemspec
80
+ - config.rb
81
+ - lib/chefdepartie.rb
82
+ - lib/chefdepartie/cookbook.rb
83
+ - lib/chefdepartie/databag.rb
84
+ - lib/chefdepartie/role.rb
85
+ - lib/chefdepartie/server.rb
86
+ - lib/chefdepartie/version.rb
87
+ homepage: https://rubygems.org/gems/chefdepartie
88
+ licenses:
89
+ - MIT
90
+ metadata: {}
91
+ post_install_message:
92
+ rdoc_options: []
93
+ require_paths:
94
+ - lib
95
+ required_ruby_version: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 2.4.6
108
+ signing_key:
109
+ specification_version: 4
110
+ summary: chefdepartie helps you test you cookbooks locally
111
+ test_files: []
112
+ has_rdoc: