summaryrefslogtreecommitdiff
path: root/chef
diff options
context:
space:
mode:
Diffstat (limited to 'chef')
-rw-r--r--chef/History.txt17
-rw-r--r--chef/LICENSE201
-rw-r--r--chef/Manifest.txt115
-rw-r--r--chef/NOTICE11
-rw-r--r--chef/README.txt78
-rw-r--r--chef/Rakefile48
-rwxr-xr-xchef/bin/chef-client65
-rwxr-xr-xchef/bin/chef-solo78
-rw-r--r--chef/config/server.rb17
-rw-r--r--chef/docs/design/HighLevel.grafflebin0 -> 1910 bytes
-rw-r--r--chef/docs/recipe.rb97
-rw-r--r--chef/examples/config.rb20
-rw-r--r--chef/examples/config/chef-solo.rb11
-rw-r--r--chef/examples/config/cookbooks/fakefile/attributes/first.rb2
-rw-r--r--chef/examples/config/cookbooks/fakefile/definitions/test.rb13
-rw-r--r--chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt1
-rw-r--r--chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt1
-rw-r--r--chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt1
-rw-r--r--chef/examples/config/cookbooks/fakefile/files/default/the_park.txt3
-rw-r--r--chef/examples/config/cookbooks/fakefile/recipes/default.rb151
-rw-r--r--chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb5
-rw-r--r--chef/examples/config/cookbooks/rubygems_server/attributes/first.rb1
-rw-r--r--chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gembin0 -> 25600 bytes
-rw-r--r--chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gembin0 -> 61440 bytes
-rw-r--r--chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gembin0 -> 117248 bytes
-rw-r--r--chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gembin0 -> 18432 bytes
-rw-r--r--chef/examples/config/cookbooks/rubygems_server/recipes/default.rb8
-rw-r--r--chef/examples/config/cookbooks/servicetest/recipes/default.rb12
-rw-r--r--chef/examples/config/cookbooks/tempfile/attributes/second.rb1
-rw-r--r--chef/examples/config/cookbooks/tempfile/recipes/default.rb24
-rw-r--r--chef/examples/config/nodes/adam.rb14
-rw-r--r--chef/examples/config/nodes/default.rb9
-rw-r--r--chef/examples/config/nodes/junglist.gen.nz.rb14
-rw-r--r--chef/examples/config/nodes/latte.rb14
-rw-r--r--chef/examples/mrepo/Rakefile0
-rw-r--r--chef/examples/node.rb17
-rw-r--r--chef/examples/node.yml134
-rw-r--r--chef/examples/sample_definition.rb82
-rw-r--r--chef/examples/sample_recipe.rb77
-rw-r--r--chef/examples/search_index/segmentsbin0 -> 16 bytes
-rw-r--r--chef/examples/search_index/segments_0bin0 -> 25 bytes
-rw-r--r--chef/examples/search_syntax.rb10
-rwxr-xr-xchef/examples/user_index.pl115
-rwxr-xr-xchef/examples/user_index.rb27
-rw-r--r--chef/lib/chef.rb29
-rw-r--r--chef/lib/chef/client.rb277
-rw-r--r--chef/lib/chef/compile.rb73
-rw-r--r--chef/lib/chef/config.rb98
-rw-r--r--chef/lib/chef/cookbook.rb111
-rw-r--r--chef/lib/chef/cookbook_loader.rb147
-rw-r--r--chef/lib/chef/couchdb.rb149
-rw-r--r--chef/lib/chef/exceptions.rb30
-rw-r--r--chef/lib/chef/file_cache.rb203
-rw-r--r--chef/lib/chef/file_store.rb135
-rw-r--r--chef/lib/chef/log.rb86
-rw-r--r--chef/lib/chef/log/formatter.rb56
-rw-r--r--chef/lib/chef/mixin/check_helper.rb31
-rw-r--r--chef/lib/chef/mixin/checksum.rb36
-rw-r--r--chef/lib/chef/mixin/command.rb206
-rw-r--r--chef/lib/chef/mixin/create_path.rb56
-rw-r--r--chef/lib/chef/mixin/from_file.rb36
-rw-r--r--chef/lib/chef/mixin/generate_url.rb46
-rw-r--r--chef/lib/chef/mixin/params_validate.rb197
-rw-r--r--chef/lib/chef/mixin/template.rb39
-rw-r--r--chef/lib/chef/node.rb260
-rw-r--r--chef/lib/chef/openid_registration.rb175
-rw-r--r--chef/lib/chef/platform.rb202
-rw-r--r--chef/lib/chef/provider.rb40
-rw-r--r--chef/lib/chef/provider/directory.rb68
-rw-r--r--chef/lib/chef/provider/execute.rb55
-rw-r--r--chef/lib/chef/provider/file.rb169
-rw-r--r--chef/lib/chef/provider/link.rb71
-rw-r--r--chef/lib/chef/provider/package.rb105
-rw-r--r--chef/lib/chef/provider/package/apt.rb89
-rw-r--r--chef/lib/chef/provider/package/portage.rb93
-rw-r--r--chef/lib/chef/provider/package/rubygems.rb116
-rw-r--r--chef/lib/chef/provider/remote_directory.rb78
-rw-r--r--chef/lib/chef/provider/remote_file.rb80
-rw-r--r--chef/lib/chef/provider/script.rb35
-rw-r--r--chef/lib/chef/provider/service.rb86
-rw-r--r--chef/lib/chef/provider/service/debian.rb53
-rw-r--r--chef/lib/chef/provider/service/init.rb95
-rw-r--r--chef/lib/chef/provider/sysctl.rb38
-rw-r--r--chef/lib/chef/provider/template.rb69
-rw-r--r--chef/lib/chef/provider/user.rb172
-rw-r--r--chef/lib/chef/provider/user/useradd.rb88
-rw-r--r--chef/lib/chef/queue.rb107
-rw-r--r--chef/lib/chef/recipe.rb127
-rw-r--r--chef/lib/chef/resource.rb172
-rw-r--r--chef/lib/chef/resource/apt_package.rb33
-rw-r--r--chef/lib/chef/resource/bash.rb33
-rw-r--r--chef/lib/chef/resource/csh.rb33
-rw-r--r--chef/lib/chef/resource/directory.rb74
-rw-r--r--chef/lib/chef/resource/execute.rb133
-rw-r--r--chef/lib/chef/resource/file.rb82
-rw-r--r--chef/lib/chef/resource/gem_package.rb33
-rw-r--r--chef/lib/chef/resource/link.rb60
-rw-r--r--chef/lib/chef/resource/package.rb69
-rw-r--r--chef/lib/chef/resource/perl.rb33
-rw-r--r--chef/lib/chef/resource/portage_package.rb33
-rw-r--r--chef/lib/chef/resource/python.rb33
-rw-r--r--chef/lib/chef/resource/remote_directory.rb79
-rw-r--r--chef/lib/chef/resource/remote_file.rb41
-rw-r--r--chef/lib/chef/resource/ruby.rb33
-rw-r--r--chef/lib/chef/resource/script.rb51
-rw-r--r--chef/lib/chef/resource/service.rb113
-rw-r--r--chef/lib/chef/resource/sysctl.rb42
-rw-r--r--chef/lib/chef/resource/template.rb49
-rw-r--r--chef/lib/chef/resource/user.rb96
-rw-r--r--chef/lib/chef/resource_collection.rb174
-rw-r--r--chef/lib/chef/resource_definition.rb67
-rw-r--r--chef/lib/chef/rest.rb150
-rw-r--r--chef/lib/chef/runner.rb100
-rw-r--r--chef/lib/chef/search.rb74
-rw-r--r--chef/lib/chef/search_index.rb80
-rw-r--r--chef/log/chef-server.log9
-rw-r--r--chef/log/merb.main.pid1
-rw-r--r--chef/log/merb_test.log7
-rw-r--r--chef/log/stompserver.pid1
-rw-r--r--chef/pkg/chef-0.0.1.gembin0 -> 265728 bytes
-rw-r--r--chef/spec/chef_server/controllers/log/merb_test.log4
-rw-r--r--chef/spec/chef_server/controllers/nodes_spec.rb232
-rw-r--r--chef/spec/chef_server/controllers/openid_consumer_spec.rb47
-rw-r--r--chef/spec/chef_server/controllers/openid_register_spec.rb111
-rw-r--r--chef/spec/chef_server/log/merb_test.log4
-rw-r--r--chef/spec/chef_server/spec.opts4
-rw-r--r--chef/spec/chef_server/spec_helper.rb16
-rw-r--r--chef/spec/data/bad-config.rb1
-rw-r--r--chef/spec/data/compile/cookbooks/test/attributes/george.rb1
-rw-r--r--chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb5
-rw-r--r--chef/spec/data/compile/cookbooks/test/recipes/default.rb5
-rw-r--r--chef/spec/data/compile/cookbooks/test/recipes/one.rb7
-rw-r--r--chef/spec/data/compile/cookbooks/test/recipes/two.rb7
-rw-r--r--chef/spec/data/compile/nodes/compile.rb5
-rw-r--r--chef/spec/data/config.rb6
-rw-r--r--chef/spec/data/cookbooks/openldap/attributes/default.rb15
-rw-r--r--chef/spec/data/cookbooks/openldap/attributes/smokey.rb1
-rw-r--r--chef/spec/data/cookbooks/openldap/definitions/client.rb5
-rw-r--r--chef/spec/data/cookbooks/openldap/definitions/server.rb5
-rw-r--r--chef/spec/data/cookbooks/openldap/ignore6
-rw-r--r--chef/spec/data/cookbooks/openldap/recipes/default.rb3
-rw-r--r--chef/spec/data/cookbooks/openldap/recipes/gigantor.rb3
-rw-r--r--chef/spec/data/cookbooks/openldap/recipes/one.rb15
-rw-r--r--chef/spec/data/cookbooks/openldap/templates/default/test.erb1
-rw-r--r--chef/spec/data/definitions/test.rb5
-rw-r--r--chef/spec/data/kitchen/openldap/attributes/default.rb3
-rw-r--r--chef/spec/data/kitchen/openldap/attributes/robinson.rb3
-rw-r--r--chef/spec/data/kitchen/openldap/definitions/client.rb3
-rw-r--r--chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb3
-rw-r--r--chef/spec/data/kitchen/openldap/recipes/gigantor.rb3
-rw-r--r--chef/spec/data/kitchen/openldap/recipes/ignoreme.rb3
-rw-r--r--chef/spec/data/kitchen/openldap/recipes/woot.rb3
-rw-r--r--chef/spec/data/nodes/default.rb15
-rw-r--r--chef/spec/data/nodes/test.example.com.rb15
-rw-r--r--chef/spec/data/nodes/test.rb15
-rw-r--r--chef/spec/data/recipes/test.rb7
-rw-r--r--chef/spec/data/seattle.txt1
-rw-r--r--chef/spec/lib/chef/provider/easy.rb37
-rw-r--r--chef/spec/lib/chef/provider/snakeoil.rb37
-rw-r--r--chef/spec/lib/chef/resource/cat.rb42
-rw-r--r--chef/spec/lib/chef/resource/zen_master.rb44
-rw-r--r--chef/spec/rcov.opts2
-rw-r--r--chef/spec/spec.opts3
-rw-r--r--chef/spec/spec_helper.rb23
-rw-r--r--chef/spec/unit/chef_spec.rb25
-rw-r--r--chef/spec/unit/client_spec.rb61
-rw-r--r--chef/spec/unit/compile_spec.rb71
-rw-r--r--chef/spec/unit/config_spec.rb91
-rw-r--r--chef/spec/unit/cookbook_loader_spec.rb117
-rw-r--r--chef/spec/unit/cookbook_spec.rb142
-rw-r--r--chef/spec/unit/couchdb_spec.rb212
-rw-r--r--chef/spec/unit/file_cache_spec.rb124
-rw-r--r--chef/spec/unit/file_store_spec.rb105
-rw-r--r--chef/spec/unit/log/formatter_spec.rb51
-rw-r--r--chef/spec/unit/log_spec.rb62
-rw-r--r--chef/spec/unit/mixin/params_validate_spec.rb331
-rw-r--r--chef/spec/unit/mixin/template_spec.rb60
-rw-r--r--chef/spec/unit/node_spec.rb326
-rw-r--r--chef/spec/unit/openid_registration_spec.rb153
-rw-r--r--chef/spec/unit/platform_spec.rb209
-rw-r--r--chef/spec/unit/provider/directory_spec.rb98
-rw-r--r--chef/spec/unit/provider/file_spec.rb224
-rw-r--r--chef/spec/unit/provider/link_spec.rb147
-rw-r--r--chef/spec/unit/provider/remote_file_spec.rb152
-rw-r--r--chef/spec/unit/provider/template_spec.rb105
-rw-r--r--chef/spec/unit/provider_spec.rb48
-rw-r--r--chef/spec/unit/queue_spec.rb105
-rw-r--r--chef/spec/unit/recipe_spec.rb143
-rw-r--r--chef/spec/unit/resource/bash_spec.rb40
-rw-r--r--chef/spec/unit/resource/csh_spec.rb40
-rw-r--r--chef/spec/unit/resource/directory_spec.rb79
-rw-r--r--chef/spec/unit/resource/execute_spec.rb102
-rw-r--r--chef/spec/unit/resource/file_spec.rb92
-rw-r--r--chef/spec/unit/resource/link_spec.rb78
-rw-r--r--chef/spec/unit/resource/package_spec.rb56
-rw-r--r--chef/spec/unit/resource/perl_spec.rb40
-rw-r--r--chef/spec/unit/resource/python_spec.rb40
-rw-r--r--chef/spec/unit/resource/remote_directory_spec.rb71
-rw-r--r--chef/spec/unit/resource/remote_file_spec.rb38
-rw-r--r--chef/spec/unit/resource/ruby_spec.rb40
-rw-r--r--chef/spec/unit/resource/script_spec.rb50
-rw-r--r--chef/spec/unit/resource/service_spec.rb100
-rw-r--r--chef/spec/unit/resource/template_spec.rb43
-rw-r--r--chef/spec/unit/resource_collection_spec.rb201
-rw-r--r--chef/spec/unit/resource_definition_spec.rb83
-rw-r--r--chef/spec/unit/resource_spec.rb144
-rw-r--r--chef/spec/unit/rest_spec.rb228
-rw-r--r--chef/spec/unit/runner_spec.rb103
-rw-r--r--chef/spec/unit/search_index_spec.rb136
-rw-r--r--chef/spec/unit/search_spec.rb74
-rw-r--r--chef/stories/chef-client26
-rw-r--r--chef/stories/chef-client.rb46
-rw-r--r--chef/stories/story_helper.rb32
-rw-r--r--chef/tasks/rspec.rb54
214 files changed, 13968 insertions, 0 deletions
diff --git a/chef/History.txt b/chef/History.txt
new file mode 100644
index 0000000000..02d903461e
--- /dev/null
+++ b/chef/History.txt
@@ -0,0 +1,17 @@
+=== 1.0.0 / 2008-03-05
+
+* 1 major enhancement
+ * Birthday!
+
+Sun Apr 27 20:30:43 PDT 2008
+ * Compiled first full recipes, and actually passed them to a provider. :)
+
+Mon Jun 9 01:57:58 PDT 2008
+ * Compiled first full recipes over a network, with registration and authorization.
+
+Thu Jul 10 19:59:46 PDT 2008
+ * Search support functional!
+
+Sat Aug 16 17:46:34 PDT 2008
+ * Compiled a template!
+ * Remote files and directories \ No newline at end of file
diff --git a/chef/LICENSE b/chef/LICENSE
new file mode 100644
index 0000000000..11069edd79
--- /dev/null
+++ b/chef/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+Copyright [yyyy] [name of copyright owner]
+
+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.
diff --git a/chef/Manifest.txt b/chef/Manifest.txt
new file mode 100644
index 0000000000..79d961a738
--- /dev/null
+++ b/chef/Manifest.txt
@@ -0,0 +1,115 @@
+History.txt
+LICENSE
+Manifest.txt
+README.txt
+Rakefile
+bin/chef-client
+bin/chef-indexer
+bin/chef-server
+bin/chef-solo
+config/chef-server.rb
+examples/config.rb
+examples/config/chef-solo.rb
+examples/config/cookbooks/fakefile/attributes/first.rb
+examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt
+examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt
+examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt
+examples/config/cookbooks/fakefile/files/default/the_park.txt
+examples/config/cookbooks/fakefile/recipes/default.rb
+examples/config/cookbooks/fakefile/templates/default/monkey.erb
+examples/config/cookbooks/tempfile/attributes/second.rb
+examples/config/cookbooks/tempfile/recipes/default.rb
+examples/config/nodes/adam.rb
+examples/config/nodes/default.rb
+examples/config/nodes/latte.rb
+examples/log/stompserver.pid
+examples/mrepo/Rakefile
+examples/node.rb
+examples/node.yml
+examples/sample_definition.rb
+examples/sample_recipe.rb
+examples/search_index/_19.cfs
+examples/search_index/_1k.cfs
+examples/search_index/_1k_0.del
+examples/search_index/_1l.cfs
+examples/search_index/_c.cfs
+examples/search_index/_c_0.del
+examples/search_index/_n.cfs
+examples/search_index/_y.cfs
+examples/search_index/segments
+examples/search_index/segments_1v
+examples/search_syntax.rb
+examples/user_index.pl
+examples/user_index.rb
+lib/chef.rb
+lib/chef/client.rb
+lib/chef/compile.rb
+lib/chef/config.rb
+lib/chef/cookbook.rb
+lib/chef/cookbook_loader.rb
+lib/chef/couchdb.rb
+lib/chef/exceptions.rb
+lib/chef/file_store.rb
+lib/chef/log.rb
+lib/chef/log/formatter.rb
+lib/chef/mixin/check_helper.rb
+lib/chef/mixin/checksum.rb
+lib/chef/mixin/command.rb
+lib/chef/mixin/from_file.rb
+lib/chef/mixin/params_validate.rb
+lib/chef/mixin/template.rb
+lib/chef/node.rb
+lib/chef/openid_registration.rb
+lib/chef/platform.rb
+lib/chef/provider.rb
+lib/chef/provider/apt.rb
+lib/chef/provider/directory.rb
+lib/chef/provider/execute.rb
+lib/chef/provider/file.rb
+lib/chef/provider/link.rb
+lib/chef/provider/package.rb
+lib/chef/provider/remote_directory.rb
+lib/chef/provider/remote_file.rb
+lib/chef/provider/script.rb
+lib/chef/provider/sysctl.rb
+lib/chef/provider/template.rb
+lib/chef/queue.rb
+lib/chef/recipe.rb
+lib/chef/resource.rb
+lib/chef/resource/bash.rb
+lib/chef/resource/csh.rb
+lib/chef/resource/directory.rb
+lib/chef/resource/execute.rb
+lib/chef/resource/file.rb
+lib/chef/resource/link.rb
+lib/chef/resource/package.rb
+lib/chef/resource/perl.rb
+lib/chef/resource/python.rb
+lib/chef/resource/remote_directory.rb
+lib/chef/resource/remote_file.rb
+lib/chef/resource/ruby.rb
+lib/chef/resource/script.rb
+lib/chef/resource/sysctl.rb
+lib/chef/resource/template.rb
+lib/chef/resource_collection.rb
+lib/chef/resource_definition.rb
+lib/chef/rest.rb
+lib/chef/runner.rb
+lib/chef/search.rb
+lib/chef/search_index.rb
+lib/chef_server/controllers/application.rb
+lib/chef_server/controllers/cookbook_files.rb
+lib/chef_server/controllers/cookbook_templates.rb
+lib/chef_server/controllers/cookbooks.rb
+lib/chef_server/controllers/exceptions.rb
+lib/chef_server/controllers/nodes.rb
+lib/chef_server/controllers/openid_consumer.rb
+lib/chef_server/controllers/openid_register.rb
+lib/chef_server/controllers/openid_server.rb
+lib/chef_server/controllers/search.rb
+lib/chef_server/controllers/search_entries.rb
+lib/chef_server/helpers/global_helpers.rb
+lib/chef_server/helpers/nodes_helper.rb
+lib/chef_server/helpers/openid_server_helpers.rb
+lib/chef_server/init.rb
+
diff --git a/chef/NOTICE b/chef/NOTICE
new file mode 100644
index 0000000000..e2385f99a2
--- /dev/null
+++ b/chef/NOTICE
@@ -0,0 +1,11 @@
+Chef NOTICE
+===========
+
+Developed at HJK Solutions (http://www.hjksolutions.com).
+
+Contributors and Copyright holders:
+
+ * Copyright 2008, Adam Jacob <adam@hjksolutions.com>
+ * Copyright 2008, Arjuna Christensen <aj@hjksolutions.com>
+ * Copyright 2008, Ezra Zygmuntowicz <ezra@engineyard.com>
+ \ No newline at end of file
diff --git a/chef/README.txt b/chef/README.txt
new file mode 100644
index 0000000000..70758161ae
--- /dev/null
+++ b/chef/README.txt
@@ -0,0 +1,78 @@
+= chef
+
+* http://oss.hjksolutions.com/chef
+
+== DESCRIPTION:
+
+Chef is a configuration management tool inspired by Puppet.
+
+I'm in ur netwerk, cookin up yer servers. :)
+
+== FEATURES/PROBLEMS:
+
+
+== SYNOPSIS:
+
+
+== REQUIREMENTS:
+
+RubyGems:
+
+* stomp
+* stompserver
+* ultraviolet
+*
+* facter
+* ferret
+* merb-core
+* haml
+* ruby-openid
+* json
+
+External Servers:
+
+* stompserver (for easy stomp mq testing)
+* CouchDB
+
+== INSTALL:
+
+Install all of the above. To fire up a develpment environment, do the following:
+
+ * Start CouchDB with 'couchdb'
+ * Start stompserver with 'stompserver'
+ * Start chef-indexer with:
+
+ ./bin/chef-indexer -l debug -c ./config/chef-server.rb
+
+ * Start chef-server on port 4000 with:
+
+ ./bin/chef-server
+
+ * Start chef-server on port 4001 with:
+
+ ./bin/chef-server -p 4001
+
+ * Test run chef with:
+
+ sudo ./bin/chef-client -l debug -c ./examples/config/chef-solo.rb
+
+== LICENSE:
+
+Chef - A configuration management system
+
+Author:: Adam Jacob (<adam@hjksolutions.com>)
+Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+License:: Apache License, Version 2.0
+
+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.
+
diff --git a/chef/Rakefile b/chef/Rakefile
new file mode 100644
index 0000000000..13738158e4
--- /dev/null
+++ b/chef/Rakefile
@@ -0,0 +1,48 @@
+# -*- ruby -*-
+require 'rubygems'
+require 'rake/gempackagetask'
+require './lib/chef.rb'
+require './tasks/rspec.rb'
+
+GEM = "chef"
+VERSION = "0.0.1"
+AUTHOR = "Adam Jacob"
+EMAIL = "adam@hjksolutions.com"
+HOMEPAGE = "http://hjksolutions.com"
+SUMMARY = "A configuration management system."
+
+spec = Gem::Specification.new do |s|
+ s.name = GEM
+ s.version = VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README.txt", "LICENSE", 'NOTICE']
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+
+ # Uncomment this to add a dependency
+ s.add_dependency "facter"
+ s.add_dependency "ruby-openid"
+ s.add_dependency "json"
+ s.add_dependency "erubis"
+ s.add_dependency "extlib"
+
+ s.bindir = "bin"
+ s.executables = %w( chef-client chef-solo )
+
+ s.require_path = 'lib'
+ s.files = %w(LICENSE README.txt Rakefile) + Dir.glob("{lib,specs,config,examples}/**/*")
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+task :install => [:package] do
+ sh %{sudo gem install pkg/#{GEM}-#{VERSION}}
+end
+
+# vim: syntax=Ruby
diff --git a/chef/bin/chef-client b/chef/bin/chef-client
new file mode 100755
index 0000000000..a80020b0aa
--- /dev/null
+++ b/chef/bin/chef-client
@@ -0,0 +1,65 @@
+#!/usr/bin/env ruby
+#
+# ./chef-client - Build a meal with chef
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+
+$: << File.join(File.dirname(__FILE__), "..", "lib")
+
+require 'optparse'
+require 'chef'
+require 'rubygems'
+require 'facter'
+
+config = {
+ :config_file => "/etc/chef/client.rb",
+ :log_level => :info,
+ :noop => false
+}
+opts = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)"
+ opts.on("-c CONFIG", "--config CONFIG", "The Chef Config file to use") do |c|
+ config[:config_file] = c
+ end
+ opts.on("-n", "--noop", "Print what you would do, but don't actually do it.") do
+ config[:noop] = true
+ end
+ opts.on_tail("-l LEVEL", "--loglevel LEVEL", "Set the log level (debug, info, warn, error, fatal)") do |l|
+ config[:log_level] = l.to_sym
+ end
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+end
+opts.parse!(ARGV)
+
+unless File.exists?(config[:config_file]) && File.readable?(config[:config_file])
+ puts "I cannot find or read the config file: #{config[:config_file]}"
+ puts opts
+ exit
+end
+
+# Load our config file
+Chef::Config.from_file(config[:config_file])
+if config[:log_level]
+ Chef::Log.level(config[:log_level].to_sym)
+end
+
+c = Chef::Client.new
+c.run
diff --git a/chef/bin/chef-solo b/chef/bin/chef-solo
new file mode 100755
index 0000000000..ecc06afb79
--- /dev/null
+++ b/chef/bin/chef-solo
@@ -0,0 +1,78 @@
+#!/usr/bin/env ruby
+#
+# ./chef-solo - Build a meal with chef, sans-server!
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+$: << File.join(File.dirname(__FILE__), "..", "lib")
+
+require 'optparse'
+require 'chef'
+require 'rubygems'
+require 'facter'
+
+config = {
+ :config_file => "/etc/chef/config.rb",
+ :log_level => :info,
+ :noop => false
+}
+opts = OptionParser.new do |opts|
+ opts.banner = "Usage: #{$0} [-d DIR|-r FILE] (options)"
+ opts.on("-c CONFIG", "--config CONFIG", "The Chef Config file to use") do |c|
+ config[:config_file] = c
+ end
+ opts.on("-n", "--noop", "Print what you would do, but don't actually do it.") do
+ config[:noop] = true
+ end
+ opts.on_tail("-l LEVEL", "--loglevel LEVEL", "Set the log level (debug, info, warn, error, fatal)") do |l|
+ config[:log_level] = l.to_sym
+ end
+ opts.on_tail("-h", "--help", "Show this message") do
+ puts opts
+ exit
+ end
+end
+opts.parse!(ARGV)
+
+unless File.exists?(config[:config_file]) && File.readable?(config[:config_file])
+ puts "I cannot find or read the config file: #{config[:config_file]}"
+ puts opts
+ exit
+end
+
+# Load our config file
+Chef::Config.from_file(config[:config_file])
+if config[:log_level]
+ Chef::Log.level(config[:log_level].to_sym)
+end
+
+# Find out our own hostname.
+node_name = Facter["fqdn"].value
+node_name ||= Facter["hostname"].value
+
+# Grab a Chef::Compile object
+compile = Chef::Compile.new()
+
+# Load our Node, and then add all the Facter facts as attributes
+compile.load_node(node_name)
+Facter.each do |field, value|
+ compile.node[field.to_sym] = value
+end
+compile.load_definitions
+compile.load_recipes
+cr = Chef::Runner.new(compile.node, compile.collection)
+cr.converge
diff --git a/chef/config/server.rb b/chef/config/server.rb
new file mode 100644
index 0000000000..74c12df403
--- /dev/null
+++ b/chef/config/server.rb
@@ -0,0 +1,17 @@
+#
+# Example Chef Server Config
+
+log_level :debug
+
+cookbook_path File.join(File.dirname(__FILE__), "..", "examples", "config", "cookbooks")
+node_path File.join(File.dirname(__FILE__), "..", "examples", "config", "nodes")
+file_store_path File.join(File.dirname(__FILE__), "..", "examples", "store")
+openid_store_path File.join(File.dirname(__FILE__), "..", "examples", "openid-db")
+openid_cstore_path File.join(File.dirname(__FILE__), "..", "examples", "openid-cstore")
+merb_log_path File.join(File.dirname(__FILE__), "..", "examples", "logs")
+search_index_path File.join(File.dirname(__FILE__), "..", "examples", "search_index")
+
+
+# openid_providers [ "localhost:4001", "openid.hjksolutions.com" ]
+
+Chef::Log::Formatter.show_time = false
diff --git a/chef/docs/design/HighLevel.graffle b/chef/docs/design/HighLevel.graffle
new file mode 100644
index 0000000000..e3a5aec187
--- /dev/null
+++ b/chef/docs/design/HighLevel.graffle
Binary files differ
diff --git a/chef/docs/recipe.rb b/chef/docs/recipe.rb
new file mode 100644
index 0000000000..b132ff42ea
--- /dev/null
+++ b/chef/docs/recipe.rb
@@ -0,0 +1,97 @@
+#namespace :openldap do
+# recipe :auth do |n|
+
+include_recipe 'openldap::client'
+include_recipe 'openssh'
+include_recipe 'nscd'
+
+file "/etc/nsswitch.conf" {
+ insure = "present"
+ owner = "root"
+ group = "root"
+ mode = 0644
+}
+
+file "/etc/ldap.conf" {
+ insure = "present"
+ owner = "root"
+ group = "root"
+ mode = 0644
+ requires = resources(:file => "/etc/nsswitch.conf")
+}
+
+file "/etc/ldap.conf" do
+ insure = "present"
+ owner = "root"
+ group = "root"
+ mode = 0644
+ requires = resources()
+end
+
+remote_file "nsswitch.conf" {
+ path "/etc/nsswitch.conf"
+ source "nsswitch.conf"
+ module "openldap"
+ mode 0644
+ owner "root"
+ group "root"
+ requires :file => "nsswitch-ldap-file", :exec => [ "one", "two" ]
+ notifies :service => "nscd", :exec => [ "nscd-clear-passwd", "nscd-clear-group" ]
+ provider 'File::Rsync'
+}
+
+remote_file "nsswitch.conf" {
+ path = "/etc/nsswitch.conf"
+ source = "nsswitch.conf"
+ module = "openldap"
+ mode = 0644
+ owner = "root"
+ group = "root"
+ requires = resources :file => "nsswitch-ldap-file",
+ :exec => [ "one", "two" ]
+ notifies = resources :service => "nscd",
+ :exec => [ "nscd-clear-passwd", "nscd-clear-group" ]
+ provider = 'File::Rsync'
+}
+
+service "nscd" do |s|
+ s.insure = "running"
+end
+
+case node[:lsbdistid]
+when "CentOS"
+ template_file "ldap.conf" do |f|
+ f.path = "/etc/ldap.conf"
+ f.content = "openldap/ldap.conf.erb"
+ f.mode = 644
+ f.owner = "root"
+ f.group = "root"
+ f.alias = "nsswitch-ldap-file"
+ f.notify = resource(:exec => [ "nscd-clear-passwd", "nscd-clear-group"] )
+ f.require = resource(:package => "nss_ldap")
+ end
+ package "nss_ldap" do |p|
+ p.insure = "latest"
+ end
+end
+
+# end
+#end
+
+definition "rails_app" do |n, args|
+ check_arguments(args, {
+ :port_number => 8000,
+ :mongrel_servers => 2,
+ :rails_environment => "production",
+ :rails_path => nil,
+ :rails_user => nil,
+ :rails_group => nil,
+ :canonical_hostname => false,
+ :template => 'rails/rails.conf.erb'
+ }
+ )
+ file "sites-#{@name}" do |f|
+
+ end
+end
+
diff --git a/chef/examples/config.rb b/chef/examples/config.rb
new file mode 100644
index 0000000000..171554c416
--- /dev/null
+++ b/chef/examples/config.rb
@@ -0,0 +1,20 @@
+#
+# Example config
+#
+
+nodes_from([
+ Chef::Node::YAML => {
+ :search_path => [ "/etc/chef/nodes" ]
+ },
+ Chef::Node::PuppetExternalNode => {
+ :command => ""
+ },
+ :rest => {
+ :search_url => "http://localhost:3000/nodes/#{node_name}"
+ },
+ :iclassify => {
+ :search_url => "http://localhost:3000/nodes/#{node_name}"
+ }
+])
+
+
diff --git a/chef/examples/config/chef-solo.rb b/chef/examples/config/chef-solo.rb
new file mode 100644
index 0000000000..2a85253b37
--- /dev/null
+++ b/chef/examples/config/chef-solo.rb
@@ -0,0 +1,11 @@
+#
+# Example Chef Solo Config
+
+cookbook_path File.join(File.dirname(__FILE__), "cookbooks")
+node_path File.join(File.dirname(__FILE__), "nodes")
+search_index_path File.join(File.dirname(__FILE__), "..", "search_index")
+log_level :info
+file_store_path "/tmp/chef"
+file_cache_path "/tmp/chef/cache"
+
+Chef::Log::Formatter.show_time = false
diff --git a/chef/examples/config/cookbooks/fakefile/attributes/first.rb b/chef/examples/config/cookbooks/fakefile/attributes/first.rb
new file mode 100644
index 0000000000..927dafe967
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/attributes/first.rb
@@ -0,0 +1,2 @@
+Chef::Log.debug("You are the first of fakefile's attributes")
+friends("you said we wuz visiting em") \ No newline at end of file
diff --git a/chef/examples/config/cookbooks/fakefile/definitions/test.rb b/chef/examples/config/cookbooks/fakefile/definitions/test.rb
new file mode 100644
index 0000000000..ce32293943
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/definitions/test.rb
@@ -0,0 +1,13 @@
+define :monkey, :eats => "bananna" do
+ file "/tmp/monkeynews-#{params[:name]}" do
+ owner "root"
+ mode 0644
+ action :create
+ end
+
+ file "/tmp/monkeynews-#{params[:name]}-second-#{params[:eats]}" do
+ owner "root"
+ mode 0644
+ notifies :touch, resources(:file => "/tmp/monkeynews-#{params[:name]}"), :immediately
+ end
+end \ No newline at end of file
diff --git a/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt
new file mode 100644
index 0000000000..9664cb3e15
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/turn/the_page.txt
@@ -0,0 +1 @@
+Classic rock is awesome.
diff --git a/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt
new file mode 100644
index 0000000000..68a3bf17e4
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/another/window.txt
@@ -0,0 +1 @@
+appleseed? \ No newline at end of file
diff --git a/chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt
new file mode 100644
index 0000000000..d4d8511934
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/files/default/remote_test/mycat.txt
@@ -0,0 +1 @@
+is a very nice kitty \ No newline at end of file
diff --git a/chef/examples/config/cookbooks/fakefile/files/default/the_park.txt b/chef/examples/config/cookbooks/fakefile/files/default/the_park.txt
new file mode 100644
index 0000000000..c71308a6ca
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/files/default/the_park.txt
@@ -0,0 +1,3 @@
+Is very pretty on a Saturday at 7:50 in August. Hottest day of the year so far in Seattle.
+
+something \ No newline at end of file
diff --git a/chef/examples/config/cookbooks/fakefile/recipes/default.rb b/chef/examples/config/cookbooks/fakefile/recipes/default.rb
new file mode 100644
index 0000000000..12ba3e69d9
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/recipes/default.rb
@@ -0,0 +1,151 @@
+execute "write-foolio" do
+ command <<-EOH
+ echo 'monkeypants #{node[:ipaddress]} #{node[:friends]}' > /tmp/foolio
+ EOH
+ user "daemon"
+end
+
+script "monkeylikesit" do
+ code %q{
+print "Woot!\n";
+open(FILE, ">", "/tmp/monkeylikesit") or die "Cannot open monkeylikesit";
+print FILE "You have some interesting hobbies #{node[:ipaddress]}";
+close(FILE);
+}
+ interpreter "perl"
+end
+
+perl "foobar" do
+ code %q{
+print "Woot!\n";
+ }
+end
+
+unless @node[:operatingsystem] == "Darwin"
+ package "emacs"
+
+ package "emacs" do
+ action :remove
+ end
+
+ package "emacs" do
+ version "22.1-0ubuntu10"
+ action :install
+ end
+
+ package "emacs" do
+ action :upgrade
+ end
+
+ package "emacs" do
+ action :purge
+ end
+end
+
+package "ruby-djbdns" do
+ action [ :install, :remove, :upgrade, :purge ]
+ provider Chef::Provider::Package::Rubygems
+end
+
+gem_package "ruby-djbdns" do
+ action [ :install, :remove, :upgrade, :purge ]
+ provider Chef::Provider::Package::Rubygems
+end
+
+file "/tmp/foo" do
+ owner "adam"
+ mode 0644
+ action :create
+ notifies :delete, resources(:file => "/tmp/glen"), :delayed
+end
+
+remote_file "/tmp/the_park.txt" do
+ owner "adam"
+ mode 0644
+ source "the_park.txt"
+ action :create
+end
+
+remote_directory "/tmp/remote_test" do
+ owner "adam"
+ mode 0755
+ source "remote_test"
+ files_owner "root"
+ files_group(node[:operatingsystem] == "Debian" ? "root" : "wheel")
+ files_mode 0644
+ files_backup false
+end
+
+template "/tmp/foo-template" do
+ owner "adam"
+ mode 0644
+ source "monkey.erb"
+ variables({
+ :one => 'two',
+ :el_che => 'rhymefest',
+ :white => {
+ :stripes => "are the best",
+ :at => "the sleazy rock thing",
+ }
+ })
+end
+
+link "/tmp/foo" do
+ link_type :symbolic
+ target_file "/tmp/xmen"
+end
+
+# 0.upto(1000) do |n|
+# file "/tmp/somefile#{n}" do
+# owner "adam"
+# mode 0644
+# action :create
+# end
+# end
+
+directory "/tmp/home" do
+ owner "root"
+ mode 0755
+ action :create
+end
+
+search(:user, "*") do |u|
+ directory "/tmp/home/#{u['name']}" do
+ if u['name'] == "nobody" && @node[:operatingsystem] == "Darwin"
+ owner "root"
+ else
+ owner "#{u['name']}"
+ end
+ mode 0755
+ action :create
+ end
+end
+
+monkey "snoopy" do
+ eats "vegetables"
+end
+
+monkey "snack"
+
+# user "katie" do
+# uid 9999
+# gid 100
+# home "/tmp/home/katie"
+# shell "/bin/bash"
+# comment "Katie Bethell"
+# action :create
+# end
+#
+# user "katie" do
+# gid 101
+# action :modify
+# end
+#
+# user "katie" do
+# shell "/home/katie"
+# action :manage
+# end
+#
+# user "katie" do
+# action [ :lock, :unlock, :remove ]
+# end
diff --git a/chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb b/chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb
new file mode 100644
index 0000000000..29c2639381
--- /dev/null
+++ b/chef/examples/config/cookbooks/fakefile/templates/default/monkey.erb
@@ -0,0 +1,5 @@
+This is a <%= @one %> with <%= @el_che %>
+
+It was rendered on a system with the ipaddress of <%= @node[:ipaddress] %>
+
+But it's all about the conquest, with <%= @node[:fqdn ] %>
diff --git a/chef/examples/config/cookbooks/rubygems_server/attributes/first.rb b/chef/examples/config/cookbooks/rubygems_server/attributes/first.rb
new file mode 100644
index 0000000000..d1b7bd6a23
--- /dev/null
+++ b/chef/examples/config/cookbooks/rubygems_server/attributes/first.rb
@@ -0,0 +1 @@
+rubygems("are awesome")
diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gem
new file mode 100644
index 0000000000..d3eafbca10
--- /dev/null
+++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-scp-1.0.1.gem
Binary files differ
diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gem
new file mode 100644
index 0000000000..e9bc3c2276
--- /dev/null
+++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-sftp-2.0.1.gem
Binary files differ
diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gem
new file mode 100644
index 0000000000..b11cbe00cc
--- /dev/null
+++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-2.0.3.gem
Binary files differ
diff --git a/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gem b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gem
new file mode 100644
index 0000000000..545f850a71
--- /dev/null
+++ b/chef/examples/config/cookbooks/rubygems_server/files/default/packages/net-ssh-gateway-1.0.0.gem
Binary files differ
diff --git a/chef/examples/config/cookbooks/rubygems_server/recipes/default.rb b/chef/examples/config/cookbooks/rubygems_server/recipes/default.rb
new file mode 100644
index 0000000000..6b60456094
--- /dev/null
+++ b/chef/examples/config/cookbooks/rubygems_server/recipes/default.rb
@@ -0,0 +1,8 @@
+remote_directory "/srv/gems" do
+ owner "www-data"
+ mode 0755
+ source "packages"
+ files_owner "www-data"
+ files_group "www-data"
+ files_mode 0644
+end
diff --git a/chef/examples/config/cookbooks/servicetest/recipes/default.rb b/chef/examples/config/cookbooks/servicetest/recipes/default.rb
new file mode 100644
index 0000000000..812b48e19d
--- /dev/null
+++ b/chef/examples/config/cookbooks/servicetest/recipes/default.rb
@@ -0,0 +1,12 @@
+service "puppet-client" do
+ service_name "puppet"
+ pattern "puppetd"
+ action :enable
+end
+
+file "/tmp/foo" do
+ owner "aj"
+ mode 0644
+ action :create
+ notifies :start, resources(:service => "puppet-client"), :immediate
+end
diff --git a/chef/examples/config/cookbooks/tempfile/attributes/second.rb b/chef/examples/config/cookbooks/tempfile/attributes/second.rb
new file mode 100644
index 0000000000..df791b8429
--- /dev/null
+++ b/chef/examples/config/cookbooks/tempfile/attributes/second.rb
@@ -0,0 +1 @@
+monkey "poots" \ No newline at end of file
diff --git a/chef/examples/config/cookbooks/tempfile/recipes/default.rb b/chef/examples/config/cookbooks/tempfile/recipes/default.rb
new file mode 100644
index 0000000000..b4fa86ac32
--- /dev/null
+++ b/chef/examples/config/cookbooks/tempfile/recipes/default.rb
@@ -0,0 +1,24 @@
+file "/tmp/glen" do
+ owner "adam"
+ mode 0755
+ action "create"
+end
+
+file "/tmp/metallica" do
+ action [ :create, :touch, :delete ]
+end
+
+directory "/tmp/marginal" do
+ owner "adam"
+ mode 0755
+ action :create
+end
+
+remote_directory "/tmp/rubygems" do
+ owner "adam"
+ mode 0755
+ source "packages"
+ files_owner "adam"
+ files_group "adam"
+ files_mode 0755
+end
diff --git a/chef/examples/config/nodes/adam.rb b/chef/examples/config/nodes/adam.rb
new file mode 100644
index 0000000000..c3e8e4bc16
--- /dev/null
+++ b/chef/examples/config/nodes/adam.rb
@@ -0,0 +1,14 @@
+##
+# Nodes should have a unique name
+##
+name "adam"
+
+##
+# Nodes can set arbitrary arguments
+##
+owner "Adam Jacob"
+
+##
+# Nodes should have recipes
+##
+recipes "tempfile", "fakefile"
diff --git a/chef/examples/config/nodes/default.rb b/chef/examples/config/nodes/default.rb
new file mode 100644
index 0000000000..d365690c60
--- /dev/null
+++ b/chef/examples/config/nodes/default.rb
@@ -0,0 +1,9 @@
+##
+# Nodes can set arbitrary arguments
+##
+owner "Adam Jacob"
+
+##
+# Nodes should have recipes
+##
+recipes "tempfile", "fakefile"
diff --git a/chef/examples/config/nodes/junglist.gen.nz.rb b/chef/examples/config/nodes/junglist.gen.nz.rb
new file mode 100644
index 0000000000..70ce7661b5
--- /dev/null
+++ b/chef/examples/config/nodes/junglist.gen.nz.rb
@@ -0,0 +1,14 @@
+##
+# Nodes should have a unique name
+##
+name "junglist.gen.nz"
+
+##
+# Nodes can set arbitrary arguments
+##
+owner "AJ Christensen"
+
+##
+# Nodes should have recipes
+##
+recipes "servicetest"
diff --git a/chef/examples/config/nodes/latte.rb b/chef/examples/config/nodes/latte.rb
new file mode 100644
index 0000000000..8987738151
--- /dev/null
+++ b/chef/examples/config/nodes/latte.rb
@@ -0,0 +1,14 @@
+##
+# Nodes should have a unique name
+##
+name "latte.local"
+
+##
+# Nodes can set arbitrary arguments
+##
+owner "Adam Jacob"
+
+##
+# Nodes should have recipes
+##
+recipes "tempfile", "fakefile"
diff --git a/chef/examples/mrepo/Rakefile b/chef/examples/mrepo/Rakefile
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/chef/examples/mrepo/Rakefile
diff --git a/chef/examples/node.rb b/chef/examples/node.rb
new file mode 100644
index 0000000000..ef1e6a73fa
--- /dev/null
+++ b/chef/examples/node.rb
@@ -0,0 +1,17 @@
+##
+# Nodes should have a unique name
+##
+name "ops1prod"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+that "is a cool ass trick"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
+
diff --git a/chef/examples/node.yml b/chef/examples/node.yml
new file mode 100644
index 0000000000..d61a14875d
--- /dev/null
+++ b/chef/examples/node.yml
@@ -0,0 +1,134 @@
+---
+parameters:
+ innodb_buffer_pool_size: 150M
+ kernel: Linux
+ serialnumber: 0123456789
+ processorcount: "2"
+ operatingsystemrelease: 2.6.18-53.1.4.el5
+ tinydns_ipaddress: 208.113.71.77
+ rubysitedir: /usr/lib/ruby/site_ruby/1.8
+ lsbrelease: ":core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3.1-ia32:graphics-3.1-noarch"
+ hardwaremodel: x86_64
+ uniqueid: 007f0100
+ ipaddress: 208.113.71.76
+ swapfree: 2.00 GB
+ puppet_class:
+ - operations-master
+ - base
+ - iptables
+ - runit
+ - puppet-client
+ - sudo
+ - zsh
+ - subversion-client
+ - ruby
+ - man
+ - openssh
+ - build-essential
+ - rsync-client
+ - munin-node
+ - resolver
+ - iclassify-agent
+ - openldap-auth
+ - openldap-client
+ - nscd
+ - perl
+ - nagios-nrpe
+ - ntp-client
+ - ntp
+ - postfix
+ - emacs
+ - centos
+ - puppet-server
+ - mysql-server
+ - djbdns-server
+ - djbdns-base
+ - djbdns-public-cache
+ - openldap-server
+ - iclassify-server
+ - apache2
+ - apache2-module-status
+ - apache2-module-alias
+ - apache2-module-auth_basic
+ - apache2-module-authn_file
+ - apache2-module-authz_default
+ - apache2-module-authz_groupfile
+ - apache2-module-authz_host
+ - apache2-module-authz_user
+ - apache2-module-autoindex
+ - apache2-module-dir
+ - apache2-module-env
+ - apache2-module-mime
+ - apache2-module-negotiation
+ - apache2-module-setenvif
+ - apache2-module-log_config
+ - perlbal
+ - mongrel-runit
+ - rails-twoohtwo
+ - mysql-client
+ - java
+ - wildcard-cert
+ - apache2-module-ldap
+ - gems-server
+ - operations-monitoring
+ - munin-moonin
+ - apache2-module-fcgid
+ - apache2-module-ssl
+ - apache2-module-authnz_ldap
+ - nagios-server
+ - subversion-server
+ - apache2-module-dav
+ - apache2-module-dav_svn
+ - trac
+ - apache2-module-proxy
+ - apache2-module-proxy_http
+ - apache2-module-rewrite
+ - apache2-module-deflate
+ - apache2-module-headers
+ puppet_env:
+ - prod
+ - prod
+ memorysize: "1.96"
+ fqdn: ops1prod.sfo.zoosk.com
+ kernelrelease: 2.6.18-53.1.4.el5
+ ps: ps -ef
+ type: Node
+ mongrel_servers: "16"
+ hardwareisa: x86_64
+ apache_listen_ports:
+ - "80"
+ - "443"
+ domain: sfo.zoosk.com
+ lsbdistdescription: CentOS release 5 (Final)
+ id: Node:1
+ tinydns_internal_ipaddress: 127.0.01
+ axfr_ipaddress: 208.113.71.77
+ macaddress_eth0: 00:30:48:96:3D:72
+ lsbdistrelease: "5"
+ memoryfree: 638.67 MB
+ manufacturer: Supermicro
+ sshrsakey: AAAAB3NzaC1yc2EAAAABIwAAAQEAzZBzuD14E6KJT3YKULXM9jEOIiC/efkn8+rK4yePLwDSNypfClZt8+ThUGlGQVZF0Z1kMa1hLfZD+Puhe9Vp4hMkTBmlvATiWEZduUvkZ7Fh56mnstjmwYoxkvP8FG/ItQtfNJY3UgmtdeeoVYw566P5E9HKKWFUQp5IpZBUpsttVuY/b7ro/ray1lY/bWJ7ykYVwcGWVjsM+W6N0je+mouLcDN3QJAMzMwVuf3MzagKcawvM6qPDJ2Lv6lxRiVn1HZwv+YoPS7+S0/wDl4tZo9+mDb1jdI++af27Q8hKUTjjyjlWUcHG9eDWYjMZPYbY5Qv6FWG6/fEwZ0zu+sVhQ==
+ lsbdistcodename: Final
+ slapd_type: master
+ macaddress_eth1: 00:30:48:96:3D:73
+ productname: PDSMi
+ mysql_server_id: "1"
+ rubyversion: 1.8.5
+ hostname: ops1prod
+ puppetversion: 0.24.1
+ public_dns_cache_ipaddress: 192.168.1.110
+ processor0: Intel(R) Xeon(R) CPU 3050 @ 2.13GHz
+ architecture: x86_64
+ facterversion: 1.3.8
+ swapsize: 2.00 GB
+ operatingsystem: CentOS
+ macaddress: 00:30:48:96:3D:72
+ processor1: Intel(R) Xeon(R) CPU 3050 @ 2.13GHz
+ ec2: "false"
+ ipaddress_eth0: 208.113.71.76
+ mongrel_port_number: "5000"
+ lsbdistid: CentOS
+ sshdsakey: AAAAB3NzaC1kc3MAAACBANC70lacMCW6+5QtqOAD7Z/GQ7ChywuX332sbZ9tk6PE6HpCy9PoEjs4MZSrYnqeXxXq2XTRwO4e9agf+6z2gzzOZP3QZj2qoVl7WgTVvvgn1c/KbKcO7/wZXKOv2qg6/vzgrgd0wYUp+1Of04uVdbxRshR2QMY7x67bKw3vvDCbAAAAFQCsW+LA/AA2JxmOCRlZC/DAZn/l4wAAAIEAi9Z1DnYg88KV4BQWGCSAdDkJ0O5maUC12GZPQUDKY+AQU+ilS9QBo3Su0Zo9rrlygNrqyVryIbH7fktz81zj410kskktT6JvUsIR5Ep6C/GTZD8+372AsVQQ+LE+Ot3PXPBntVq2uRRum9wRKul2EZ8+uOhh3gZV2l4WdEg/07sAAACANm3CUM/+grYwDg0Uv6dNgcwsQsXHxfSUbPHGVdiEuf7WjQhNgVQffEwGHOS1xvfv2nvbGFnTnTDuIOo45vhBUmbv9s7NuRhn++M8ZwL+pLAq7BbFCeP4y0WvKOTJdupiLrgVH3KGRFP0djpOagueNQr9zcscTJR3eagImkb8aPs=
+ ipaddress_eth1: 192.168.1.110
+classes:
+- operations-master
diff --git a/chef/examples/sample_definition.rb b/chef/examples/sample_definition.rb
new file mode 100644
index 0000000000..f536369290
--- /dev/null
+++ b/chef/examples/sample_definition.rb
@@ -0,0 +1,82 @@
+web_server "monchichi" do
+ one "something"
+ two "something else"
+end
+
+runit_service "bobo" do
+ directory "monkey"
+ downif "/bin/false is true"
+ templatedir "something"
+end
+
+define :runit_service, :directory => "/etc/sv", :downif => "/bin/false", :templatedir => nil do
+ require_recipe "runit"
+
+ validate(
+ params,
+ {
+ :directory => { :required => true },
+ :downif => { :required => true },
+ :templatedir => { :required => false },
+ }
+ )
+
+ file "#{param[:directory]}-#{param[:name]}" do
+ path "#{param[:directory]}/#{param[:name]}"
+ insure "directory"
+ owner "root"
+ group "root"
+ mode 0755
+ end
+
+ file "#{param[:directory]}/#{param[:name]}/log" do
+ insure "directory"
+ owner "root"
+ group "root"
+ mode 0755
+ end
+
+ file "#{param[:directory]}/#{param[:name]}/log/main" do
+ insure "directory"
+ owner "root"
+ group "root"
+ mode 0755
+ end
+
+ symlink "/etc/init.d/#{param[:name]}" do
+ sv_dir = case node[:lsbdistid]
+ when 'CentOS': "/usr/local/bin/sv"
+ else: "/usr/bin/sv"
+ end
+ source_file = sv_dir
+ end
+
+ symlink "/var/service/#{param[:name]}" do
+ source_file "#{param[:directory]}/#{param[:name]}"
+ end
+
+ service "#{param[:name]}" do
+ supports :status => true, :restart => true
+ end
+
+ template_file "#{param[:directory]}/#{param[:name]}/log/run" do
+ content "#{param[:templatedir]}/log-run.erb"
+ owner "root"
+ group "root"
+ mode 755
+ notifies resource("service[#{param[:name]}]")
+ end
+
+ template_file "#{param[:directory]}/#{param[:name]}/run" do
+ content "#{param[:templatedir]}/run.erb"
+ owner root
+ group root
+ mode 755
+ notifies resource("service[#{param[:name]}]")
+ end
+
+ exec "#{param[:name]}-down" do
+ command "/etc/init.d/#{param[:name]} down"
+ only_if "#{downif}"
+ end
+end
diff --git a/chef/examples/sample_recipe.rb b/chef/examples/sample_recipe.rb
new file mode 100644
index 0000000000..6abb31bfcd
--- /dev/null
+++ b/chef/examples/sample_recipe.rb
@@ -0,0 +1,77 @@
+require_recipe "openldap"
+require_recipe "openldap::client"
+require_recipe "openldap::server"
+require_recipe "resolver"
+require_recipe "base"
+
+exec "restart-apache" do
+ path "/usr/bin:/usr/local/bin"
+ command "/etc/init.d/apache2 restart"
+ action :nothing
+end
+
+service "apache2" do
+ insure "running"
+ has_restart true
+end
+
+file "/etc/nsswitch.conf" do
+ owner "root"
+ group "root"
+ mode 0644
+ notifies :restart, resources("service[openldap]"), :immediately
+end
+
+service "apache2" do
+ action "enabled"
+ subscribes :restart, resources("/etc/nsswitch.conf"), :immediately
+end
+
+file "/etc/ldap.conf" do
+ owner "root"
+ group "root"
+ mode 0644
+end
+
+file "/srv/monkey" do
+ insure "present"
+ owner "root"
+ group "root"
+ mode 0644
+end
+
+file "/srv/owl" do
+ insure "present"
+ owner "root"
+ group "root"
+ mode 0644
+end
+
+file "/srv/zen" do
+ insure "absent"
+end
+
+#
+# file "/srv/monkey" do |f|
+# f.insure = "present"
+# f.owner = "adam"
+# f.group = "adam"
+# f.mode = 0644
+# f.before = resources(:file => "/etc/nsswitch.conf")
+# end
+#
+# file "/etc/ldap-nss.conf" do |f|
+# f.insure = "present"
+# f.owner = "root"
+# f.group = "root"
+# f.mode = 0644
+# f.notifies = :refresh, resources(:file => "/etc/ldap.conf")
+# end
+#
+# file "/etc/coffee.conf" do |f|
+# f.insure = "present"
+# f.owner = "root"
+# f.group = "root"
+# f.mode = 0644
+# f.subscribes = :polio, resources(:file => "/etc/nsswitch.conf")
+# end \ No newline at end of file
diff --git a/chef/examples/search_index/segments b/chef/examples/search_index/segments
new file mode 100644
index 0000000000..01d633b27e
--- /dev/null
+++ b/chef/examples/search_index/segments
Binary files differ
diff --git a/chef/examples/search_index/segments_0 b/chef/examples/search_index/segments_0
new file mode 100644
index 0000000000..b642403384
--- /dev/null
+++ b/chef/examples/search_index/segments_0
Binary files differ
diff --git a/chef/examples/search_syntax.rb b/chef/examples/search_syntax.rb
new file mode 100644
index 0000000000..4be57b7802
--- /dev/null
+++ b/chef/examples/search_syntax.rb
@@ -0,0 +1,10 @@
+search(:users, "allowed:#{node[:hostname]} or allowed:#{node[:tags]}") do |u|
+ user "#{u['username']}" do
+ uid "#{u['uid']}"
+ gid "#{u['gid']}"
+ username "#{u['username']}"
+ homedir "#{u['homedir']}"
+ action :create
+ end
+end
+
diff --git a/chef/examples/user_index.pl b/chef/examples/user_index.pl
new file mode 100755
index 0000000000..e78a8125f4
--- /dev/null
+++ b/chef/examples/user_index.pl
@@ -0,0 +1,115 @@
+#!/usr/bin/perl
+#
+# M00se on the L00se
+
+package Chef::Rest;
+
+use strict;
+use warnings;
+
+use LWP::UserAgent;
+use URI;
+use Params::Validate qw(:all);
+use JSON::Syck;
+
+sub new {
+ my $self = shift;
+ my %p = validate(@_,
+ {
+ content_type => { type => SCALAR },
+ },
+ );
+ my $ref = {
+ 'ua' => LWP::UserAgent->new,
+ 'content_type' => $p{'content_type'},
+ };
+ bless $ref, $self;
+}
+
+sub load {
+ my $self = shift;
+ my $data = shift;
+ return JSON::Syck::Load($data);
+}
+
+sub get {
+ my $self = shift;
+ my %p = validate(@_,
+ {
+ url => { type => SCALAR },
+ params => { type => ARRAYREF, optional => 1 },
+ },
+ );
+
+ my $url = URI->new($p{'url'});
+ if (defined($p{'params'})) {
+ $url->query_form($p{'params'});
+ }
+ my $req = HTTP::Request->new('GET' => $url);
+ $req->content_type($self->{'content_type'});
+ return $self->ua->request($req);
+}
+
+sub delete {
+ my $self = shift;
+ my %p = validate(@_,
+ {
+ url => { type => SCALAR },
+ },
+ );
+ my $req = HTTP::Request->new('DELETE' => $p{'url'});
+ $req->content_type($self->{'content_type'});
+ return $self->ua->request($req);
+}
+
+sub put {
+ my $self = shift;
+ my %p = validate(@_,
+ {
+ url => { type => SCALAR },
+ data => 1,
+ },
+ );
+ my $data = JSON::Syck::Dump($p{'data'});
+ my $req = HTTP::Request->new('PUT' => $p{'url'});
+ $req->content_type($self->{'content_type'});
+ $req->content_length(do { use bytes; length($data) });
+ $req->content($data);
+ return $self->ua->request($req);
+}
+
+sub post {
+ my $self = shift;
+ my %p = validate(@_,
+ {
+ url => { type => SCALAR },
+ data => { required => 1 },
+ },
+ );
+ my $data = JSON::Syck::Dump($p{'data'});
+ my $req = HTTP::Request->new('POST' => $p{'url'});
+ $req->content_type($self->{'content_type'});
+ $req->content_length(do { use bytes; length($data) });
+ $req->content($data);
+ return $self->{ua}->request($req);
+}
+
+my $rest = Chef::Rest->new(content_type => 'application/json');
+
+while (my @passwd = getpwent) {
+ print "Ensuring we have $passwd[0]\n";
+ $rest->post(
+ url => 'http://localhost:4000/search/user/entries',
+ data => {
+ id => $passwd[0],
+ name => $passwd[0],
+ uid => $passwd[2],
+ gid => $passwd[3],
+ gecos => $passwd[6],
+ dir => $passwd[7],
+ shell => $passwd[8],
+ change => '',
+ expire => $passwd[9],
+ }
+ )
+}
diff --git a/chef/examples/user_index.rb b/chef/examples/user_index.rb
new file mode 100755
index 0000000000..485cff81b8
--- /dev/null
+++ b/chef/examples/user_index.rb
@@ -0,0 +1,27 @@
+#!/usr/bin/env ruby
+#
+# Create a users index, based on /etc/passwd
+
+require 'etc'
+require File.join(File.dirname(__FILE__), "..", "lib", "chef")
+
+Chef::Config[:log_level] = :info
+r = Chef::REST.new("http://localhost:4000")
+
+users = Array.new
+Etc.passwd do |passwd|
+ Chef::Log.info("Ensuring we have #{passwd.name}")
+ r.post_rest("search/user/entries",
+ {
+ :id => passwd.name,
+ :name => passwd.name,
+ :uid => passwd.uid,
+ :gid => passwd.gid,
+ :gecos => passwd.gecos,
+ :dir => passwd.dir,
+ :shell => passwd.shell,
+ :change => passwd.change,
+ :expire => passwd.expire
+ }
+ )
+end
diff --git a/chef/lib/chef.rb b/chef/lib/chef.rb
new file mode 100644
index 0000000000..ac7f43adef
--- /dev/null
+++ b/chef/lib/chef.rb
@@ -0,0 +1,29 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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'
+
+Dir[
+ File.join(
+ File.dirname(__FILE__),
+ 'chef/**/*.rb'
+ )].sort.each { |lib| require lib unless lib =~ /server/ }
+
+class Chef
+ VERSION = '0.0.1'
+end
diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb
new file mode 100644
index 0000000000..fd7e263ce0
--- /dev/null
+++ b/chef/lib/chef/client.rb
@@ -0,0 +1,277 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require File.join(File.dirname(__FILE__), "mixin", "generate_url")
+require File.join(File.dirname(__FILE__), "mixin", "checksum")
+
+require 'rubygems'
+require 'facter'
+
+class Chef
+ class Client
+
+ include Chef::Mixin::GenerateURL
+ include Chef::Mixin::Checksum
+
+ attr_accessor :node, :registration, :safe_name
+
+ # Creates a new Chef::Client.
+ def initialize()
+ @node = nil
+ @safe_name = nil
+ @registration = nil
+ @rest = Chef::REST.new(Chef::Config[:registration_url])
+ end
+
+ # Do a full run for this Chef::Client. Calls:
+ #
+ # * build_node - Get the last known state, merge with local changes
+ # * register - Make sure we have an openid
+ # * authenticate - Authenticate with our openid
+ # * sync_definitions - Populate the local cache with all the definitions
+ # * sync_recipes - Populate the local cache with all the recipes
+ # * do_attribute_files - Populate the local cache with all attributes, and execute them
+ # * save_node - Store the new node configuration
+ # * converge - Bring this system up to date, based on the local cache
+ # * save_node - Store the node again, in case convergence altered future state
+ #
+ # === Returns
+ # true:: Always returns true.
+ def run
+ build_node
+ register
+ authenticate
+ sync_definitions
+ sync_recipes
+ do_attribute_files
+ save_node
+ converge
+ save_node
+ true
+ end
+
+ # Builds a new node object for this client. Starts with querying for the FQDN of the current
+ # host (unless it is supplied), then merges in the facts from Facter.
+ #
+ # === Parameters
+ # node_name<String>:: The name of the node to build - defaults to nil
+ #
+ # === Returns
+ # node:: Returns the created node object, also stored in @node
+ def build_node(node_name=nil)
+ node_name ||= Facter["fqdn"].value ? Facter["fqdn"].value : Facter["hostname"].value
+ @safe_name = node_name.gsub(/\./, '_')
+ Chef::Log.debug("Building node object for #{@safe_name}")
+ begin
+ @node = @rest.get_rest("nodes/#{@safe_name}")
+ rescue Net::HTTPServerException => e
+ unless e.message =~ /^404/
+ raise e
+ end
+ end
+ unless @node
+ @node ||= Chef::Node.new
+ @node.name(node_name)
+ end
+ Facter.each do |field, value|
+ @node[field] = value
+ end
+ @node
+ end
+
+ # If this node has been registered before, this method will fetch the current registration
+ # data.
+ #
+ # If it has not, we register it by calling create_registration.
+ #
+ # === Returns
+ # true:: Always returns true
+ def register
+ Chef::Log.debug("Registering #{@safe_name} for an openid")
+ @registration = nil
+ begin
+ @registration = @rest.get_rest("registrations/#{@safe_name}")
+ rescue Net::HTTPServerException => e
+ unless e.message =~ /^404/
+ raise e
+ end
+ end
+
+ if @registration
+ reg = Chef::FileStore.load("registration", @safe_name)
+ @secret = reg["secret"]
+ else
+ create_registration
+ end
+ true
+ end
+
+ # Generates a random secret, stores it in the Chef::Filestore with the "registration" key,
+ # and posts our nodes registration information to the server.
+ #
+ # === Returns
+ # true:: Always returns true
+ def create_registration
+ @secret = random_password(500)
+ Chef::FileStore.store("registration", @safe_name, { "secret" => @secret })
+ @rest.post_rest("registrations", { :id => @safe_name, :password => @secret })
+ true
+ end
+
+ # Authenticates the node via OpenID.
+ #
+ # === Returns
+ # true:: Always returns true
+ def authenticate
+ Chef::Log.debug("Authenticating #{@safe_name} via openid")
+ response = @rest.post_rest('openid/consumer/start', {
+ "openid_identifier" => "#{Chef::Config[:openid_url]}/openid/server/node/#{@safe_name}",
+ "submit" => "Verify"
+ })
+ @rest.post_rest(
+ "#{Chef::Config[:openid_url]}#{response["action"]}",
+ { "password" => @secret }
+ )
+ end
+
+ # Update the file caches for a given cache segment. Takes a segment name
+ # and a hash that matches one of the cookbooks/_attribute_files style
+ # remote file listings.
+ #
+ # === Parameters
+ # segment<String>:: The cache segment to update
+ # remote_list<Hash>:: A cookbooks/_attribute_files style remote file listing
+ def update_file_cache(segment, remote_list)
+ # We need the list of known good attribute files, so we can delete any that are
+ # just laying about.
+ file_canonical = Hash.new
+
+ remote_list.each do |rf|
+ cache_file = File.join("cookbooks", rf['cookbook'], segment, rf['name'])
+ file_canonical[cache_file] = true
+
+ current_checksum = nil
+ if Chef::FileCache.has_key?(cache_file)
+ current_checksum = checksum(Chef::FileCache.load(cache_file, false))
+ end
+
+ rf_url = generate_cookbook_url(
+ rf['name'],
+ rf['cookbook'],
+ segment,
+ @node,
+ current_checksum ? { 'checksum' => current_checksum } : nil
+ )
+ Chef::Log.debug(rf_url)
+
+ changed = true
+ begin
+ raw_file = @rest.get_rest(rf_url, true)
+ rescue Net::HTTPRetriableError => e
+ if e.response.kind_of?(Net::HTTPNotModified)
+ changed = false
+ Chef::Log.debug("Cache file #{cache_file} is unchanged")
+ else
+ raise e
+ end
+ end
+
+ if changed
+ Chef::Log.info("Storing updated #{cache_file} in the cache.")
+ Chef::FileCache.move_to(raw_file.path, cache_file)
+ end
+ end
+
+ Chef::FileCache.list.each do |cache_file|
+ if cache_file.match("cookbooks/.+?/#{segment}")
+ unless file_canonical[cache_file]
+ Chef::Log.info("Removing #{cache_file} from the cache; it is no longer on the server.")
+ Chef::FileCache.delete(cache_file)
+ end
+ end
+ end
+
+ end
+
+ # Gets all the attribute files included in all the cookbooks available on the server,
+ # and executes them.
+ #
+ # === Returns
+ # true:: Always returns true
+ def do_attribute_files
+ Chef::Log.debug("Synchronizing attributes")
+ update_file_cache("attributes", @rest.get_rest('cookbooks/_attribute_files'))
+ Chef::FileCache.list.each do |cache_file|
+ if cache_file.match("cookbooks/.+?/attributes")
+ Chef::Log.debug("Executing #{cache_file}")
+ @node.from_file(Chef::FileCache.load(cache_file, false))
+ end
+ end
+ true
+ end
+
+ def sync_definitions
+ Chef::Log.debug("Synchronizing definitions")
+ update_file_cache("definitions", @rest.get_rest('cookbooks/_definition_files'))
+ end
+
+ def sync_recipes
+ Chef::Log.debug("Synchronizing attributes")
+ update_file_cache("recipes", @rest.get_rest('cookbooks/_recipe_files'))
+ end
+
+ # Updates the current node configuration on the server.
+ #
+ # === Returns
+ # true:: Always returns true
+ def save_node
+ Chef::Log.debug("Saving the current state of node #{@safe_name}")
+ @node = @rest.put_rest("nodes/#{@safe_name}", @node)
+ true
+ end
+
+ # Compiles the full list of recipes for the server, and passes it to an instance of
+ # Chef::Runner.converge.
+ #
+ # === Returns
+ # true:: Always returns true
+ def converge
+ Chef::Log.debug("Compiling recipes for node #{@safe_name}")
+ Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks")
+ compile = Chef::Compile.new()
+ compile.node = @node
+ compile.load_definitions
+ compile.load_recipes
+
+ Chef::Log.debug("Executing recipes for node #{@safe_name}")
+ cr = Chef::Runner.new(@node, compile.collection)
+ cr.converge
+ true
+ end
+
+ protected
+ # Generates a random password of "len" length.
+ def random_password(len)
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ newpass = ""
+ 1.upto(len) { |i| newpass << chars[rand(chars.size-1)] }
+ newpass
+ end
+
+ end
+end
diff --git a/chef/lib/chef/compile.rb b/chef/lib/chef/compile.rb
new file mode 100644
index 0000000000..d5f38368ed
--- /dev/null
+++ b/chef/lib/chef/compile.rb
@@ -0,0 +1,73 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ class Compile
+
+ attr_accessor :node, :cookbook_loader, :collection, :definitions
+
+ # Creates a new Chef::Compile object. This object gets used by the Chef Server to generate
+ # a fully compiled recipe list for a node.
+ def initialize()
+ @node = nil
+ @cookbook_loader = Chef::CookbookLoader.new
+ @collection = Chef::ResourceCollection.new
+ @definitions = Hash.new
+ end
+
+ # Looks up the node via the "name" argument, first from CouchDB, then by calling
+ # Chef::Node.find_file(name)
+ #
+ # The first step in compiling the catalog. Results available via the node accessor.
+ def load_node(name)
+ Chef::Log.debug("Loading Chef Node #{name} from CouchDB")
+ @node = Chef::Node.load(name)
+ Chef::Log.debug("Loading Recipe for Chef Node #{name}")
+ @node.find_file(name)
+ @node
+ end
+
+ # Load all the definitions, from every cookbook, so they are available when we process
+ # the recipes.
+ #
+ # Results available via the definitions accessor.
+ def load_definitions()
+ @cookbook_loader.each do |cookbook|
+ hash = cookbook.load_definitions
+ @definitions.merge!(hash)
+ end
+ end
+
+ # Load all the recipes specified in the node data (loaded via load_node, above.)
+ #
+ # The results are available via the collection accessor (which returns a Chef::ResourceCollection
+ # object)
+ def load_recipes
+ @node.recipes.each do |recipe|
+ rmatch = recipe.match(/(.+?)::(.+)/)
+ if rmatch
+ cookbook = @cookbook_loader[rmatch[1]]
+ cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader)
+ else
+ cookbook = @cookbook_loader[recipe]
+ cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader)
+ end
+ end
+ end
+
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb
new file mode 100644
index 0000000000..573cf9dfb7
--- /dev/null
+++ b/chef/lib/chef/config.rb
@@ -0,0 +1,98 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "check_helper")
+require File.join(File.dirname(__FILE__), "mixin", "from_file")
+
+# Chef::Config[:variable]
+# @config = Chef::Config.new()
+#
+# Chef::ConfigFast << Chef::Config
+#
+# Chef::Config.from_file(foo)
+# Chef::Resource.from_file (NoMethodError)
+# Chef::Config[:cookbook_path]
+# Chef::Config.cookbook_path
+# Chef::Config.cookbook_path "one", "two"
+
+class Chef
+ class Config
+ include Chef::Mixin::CheckHelper
+
+ @configuration = {
+ :cookbook_path => [ "/etc/chef/site-cookbook", "/etc/chef/cookbook" ],
+ :node_path => "/etc/chef/node",
+ :file_store_path => "/var/chef/store",
+ :search_index_path => "/var/chef/search_index",
+ :log_level => :info,
+ :log_location => STDOUT,
+ :merb_log_path => "/var/log/chef/merb.log",
+ :openid_providers => nil,
+ :ssl_verify_mode => :verify_none,
+ :rest_timeout => 60,
+ :couchdb_url => "http://localhost:5984",
+ :registration_url => "http://localhost:4000",
+ :openid_url => "http://localhost:4001",
+ :template_url => "http://localhost:4000",
+ :remotefile_url => "http://localhost:4000",
+ :search_url => "http://localhost:4000",
+ :couchdb_database => "chef",
+ :openid_store_path => "/var/chef/openid/db",
+ :openid_cstore_path => "/var/chef/openid/cstore",
+ :file_cache_path => "/var/chef/cache",
+ :executable_path => ENV['PATH'] ? ENV['PATH'].split(File::PATH_SEPARATOR) : []
+ }
+
+ class << self
+ include Chef::Mixin::FromFile
+
+ def configure(&block)
+ yield @configuration
+ end
+
+ def [](config_option)
+ if @configuration.has_key?(config_option.to_sym)
+ @configuration[config_option.to_sym]
+ else
+ raise ArgumentError, "Cannot find configuration option #{config_option.to_s}"
+ end
+ end
+
+ def []=(config_option, value)
+ @configuration[config_option.to_sym] = value
+ end
+
+ def has_key?(key)
+ @configuration.has_key?(key.to_sym)
+ end
+
+ def method_missing(method_symbol, *args)
+ if @configuration.has_key?(method_symbol)
+ if args.length == 1
+ @configuration[method_symbol] = args[0]
+ elsif args.length > 1
+ @configuration[method_symbol] = args
+ end
+ return @configuration[method_symbol]
+ else
+ raise ArgumentError, "Cannot find configuration option #{method_symbol.to_s}"
+ end
+ end
+
+ end # class << self
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/cookbook.rb b/chef/lib/chef/cookbook.rb
new file mode 100644
index 0000000000..46fc956d83
--- /dev/null
+++ b/chef/lib/chef/cookbook.rb
@@ -0,0 +1,111 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ class Cookbook
+
+ attr_accessor :attribute_files, :definition_files, :template_files, :remote_files, :name
+ attr_reader :recipe_files
+
+ def initialize(name)
+ @name = name
+ @attribute_files = Array.new
+ @definition_files = Array.new
+ @template_files = Array.new
+ @remote_files = Array.new
+ @recipe_files = Array.new
+ @recipe_names = Hash.new
+ @loaded_attributes = false
+ end
+
+ def load_attributes(node)
+ unless node.kind_of?(Chef::Node)
+ raise ArgumentError, "You must pass a Chef::Node to load_attributes!"
+ end
+ @attribute_files.each do |file|
+ node.from_file(file)
+ end
+ @loaded_atributes = true
+ node
+ end
+
+ def load_definitions
+ results = Hash.new
+ @definition_files.each do |file|
+ Chef::Log.debug("Loading cookbook #{name}'s definitions from #{file}")
+ resourcedef = Chef::ResourceDefinition.new
+ resourcedef.from_file(file)
+ results[resourcedef.name] = resourcedef
+ end
+ results
+ end
+
+ def recipe_files=(*args)
+ @recipe_files = args.flatten
+ @recipe_files.each_index do |i|
+ file = @recipe_files[i]
+ case file
+ when /(.+\/)(.+).rb$/
+ @recipe_names[$2] = i
+ when /(.+).rb$/
+ @recipe_names[$1] = i
+ else
+ @recipe_names[file] = i
+ end
+ end
+ @recipe_files
+ end
+
+ def recipe?(name)
+ lookup_name = name
+ if name =~ /(.+)::(.+)/
+ cookbook_name = $1
+ lookup_name = $2
+ return false unless cookbook_name == @name
+ end
+ @recipe_names.has_key?(lookup_name)
+ end
+
+ def recipes
+ results = Array.new
+ @recipe_names.each_key do |rname|
+ results << "#{@name}::#{rname}"
+ end
+ results
+ end
+
+ def load_recipe(name, node, collection=nil, definitions=nil, cookbook_loader=nil)
+ cookbook_name = @name
+ recipe_name = nil
+ nmatch = name.match(/^(.+?)::(.+)$/)
+ recipe_name = nmatch ? nmatch[2] : name
+
+ unless @recipe_names.has_key?(recipe_name)
+ raise ArgumentError, "Cannot find a recipe matching #{recipe_name} in cookbook #{@name}"
+ end
+ Chef::Log.debug("Found recipe #{recipe_name} in cookbook #{cookbook_name}") if Chef::Log.debug?
+ unless @loaded_attributes
+ load_attributes(node)
+ end
+ recipe = Chef::Recipe.new(cookbook_name, recipe_name, node,
+ collection, definitions, cookbook_loader)
+ recipe.from_file(@recipe_files[@recipe_names[recipe_name]])
+ recipe
+ end
+
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb
new file mode 100644
index 0000000000..6f6c8d8010
--- /dev/null
+++ b/chef/lib/chef/cookbook_loader.rb
@@ -0,0 +1,147 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ class CookbookLoader
+
+ attr_accessor :cookbook
+
+ include Enumerable
+
+ def initialize()
+ @cookbook = Hash.new
+ load_cookbooks
+ end
+
+ def load_cookbooks
+ cookbook_settings = Hash.new
+ Chef::Config.cookbook_path.each do |cb_path|
+ Dir[File.join(cb_path, "*")].each do |cookbook|
+ next unless File.directory?(cookbook)
+ cookbook_name = File.basename(cookbook).to_sym
+ unless cookbook_settings.has_key?(cookbook_name)
+ cookbook_settings[cookbook_name] = {
+ :ignore_regexes => Array.new,
+ :attribute_files => Array.new,
+ :definition_files => Array.new,
+ :recipe_files => Array.new,
+ :template_files => Array.new,
+ :remote_files => Array.new,
+ }
+ end
+ ignore_regexes = load_ignore_file(File.join(cookbook, "ignore"))
+ cookbook_settings[cookbook_name][:ignore_regexes].concat(ignore_regexes)
+ load_files_unless_basename(
+ File.join(cookbook, "attributes", "*.rb"),
+ cookbook_settings[cookbook_name][:attribute_files],
+ cookbook_settings[cookbook_name][:ignore_regexes]
+ )
+ load_files_unless_basename(
+ File.join(cookbook, "definitions", "*.rb"),
+ cookbook_settings[cookbook_name][:definition_files],
+ cookbook_settings[cookbook_name][:ignore_regexes]
+ )
+ load_files_unless_basename(
+ File.join(cookbook, "recipes", "*.rb"),
+ cookbook_settings[cookbook_name][:recipe_files],
+ cookbook_settings[cookbook_name][:ignore_regexes]
+ )
+ load_cascading_files(
+ File.join(cookbook, "templates", "**", "*.erb"),
+ File.join(cookbook, "templates"),
+ cookbook_settings[cookbook_name][:template_files],
+ cookbook_settings[cookbook_name][:ignore_regexes]
+ )
+ load_cascading_files(
+ File.join(cookbook, "files", "**", "*"),
+ File.join(cookbook, "files"),
+ cookbook_settings[cookbook_name][:remote_files],
+ cookbook_settings[cookbook_name][:ignore_regexes]
+ )
+ end
+ end
+ cookbook_settings.each_key do |cookbook|
+ @cookbook[cookbook] = Chef::Cookbook.new(cookbook)
+ @cookbook[cookbook].attribute_files = cookbook_settings[cookbook][:attribute_files]
+ @cookbook[cookbook].definition_files = cookbook_settings[cookbook][:definition_files]
+ @cookbook[cookbook].recipe_files = cookbook_settings[cookbook][:recipe_files]
+ @cookbook[cookbook].template_files = cookbook_settings[cookbook][:template_files]
+ @cookbook[cookbook].remote_files = cookbook_settings[cookbook][:remote_files]
+ end
+ end
+
+ def [](cookbook)
+ if @cookbook.has_key?(cookbook.to_sym)
+ @cookbook[cookbook.to_sym]
+ else
+ raise ArgumentError, "Cannot find a cookbook named #{cookbook.to_s}"
+ end
+ end
+
+ def each
+ @cookbook.each_value do |cobject|
+ yield cobject
+ end
+ end
+
+ private
+
+ def load_ignore_file(ignore_file)
+ results = Array.new
+ if File.exists?(ignore_file) && File.readable?(ignore_file)
+ IO.foreach(ignore_file) do |line|
+ next if line =~ /^#/
+ next if line =~ /^\w*$/
+ line.chomp!
+ results << Regexp.new(line)
+ end
+ end
+ results
+ end
+
+ def load_cascading_files(file_glob, base_path, result_array, ignore_regexes)
+ Dir[file_glob].each do |file|
+ next if skip_file(file, ignore_regexes)
+ file =~ /^#{base_path}\/(.+)$/
+ singlecopy = $1
+ unless result_array.detect { |f| f =~ /#{singlecopy}$/ }
+ result_array << file
+ end
+ end
+ end
+
+ def load_files_unless_basename(file_glob, result_array, ignore_regexes)
+ Dir[file_glob].each do |file|
+ next if skip_file(file, ignore_regexes)
+ file_basename = File.basename(file)
+ # If we've seen a file with this basename before, skip it.
+ unless result_array.detect { |f| File.basename(f) == file_basename }
+ result_array << file
+ end
+ end
+ end
+
+ def skip_file(file, ignore_regexes)
+ skip = false
+ ignore_regexes.each do |exp|
+ skip = true if exp.match(file)
+ end
+ skip
+ end
+
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/couchdb.rb b/chef/lib/chef/couchdb.rb
new file mode 100644
index 0000000000..22a651ca48
--- /dev/null
+++ b/chef/lib/chef/couchdb.rb
@@ -0,0 +1,149 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require 'digest/sha2'
+require 'json'
+
+class Chef
+ class CouchDB
+ include Chef::Mixin::ParamsValidate
+
+ def initialize(url=nil)
+ url ||= Chef::Config[:couchdb_url]
+ @rest = Chef::REST.new(url)
+ end
+
+ def create_db
+ @database_list = @rest.get_rest("_all_dbs")
+ unless @database_list.detect { |db| db == Chef::Config[:couchdb_database] }
+ response = @rest.put_rest(Chef::Config[:couchdb_database], Hash.new)
+ end
+ Chef::Config[:couchdb_database]
+ end
+
+ def create_design_document(name, data)
+ to_update = true
+ begin
+ old_doc = @rest.get_rest("#{Chef::Config[:couchdb_database]}/_design%2F#{name}")
+ if data["version"] != old_doc["version"]
+ data["_rev"] = old_doc["_rev"]
+ Chef::Log.debug("Updating #{name} views")
+ else
+ to_update = false
+ end
+ rescue
+ Chef::Log.debug("Creating #{name} views for the first time")
+ end
+ if to_update
+ @rest.put_rest("#{Chef::Config[:couchdb_database]}/_design%2F#{name}", data)
+ end
+ true
+ end
+
+ def store(obj_type, name, object)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ :object => object,
+ },
+ {
+ :object => { :respond_to => :to_json },
+ }
+ )
+ @rest.put_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}", object)
+ end
+
+ def load(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}")
+ end
+
+ def delete(obj_type, name, rev=nil)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ unless rev
+ last_obj = @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}")
+ if last_obj.respond_to?(:couchdb_rev)
+ rev = last_obj.couchdb_rev
+ else
+ rev = last_obj['_rev']
+ end
+ end
+ @rest.delete_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}?rev=#{rev}")
+ end
+
+ def list(view, inflate=false)
+ validate(
+ {
+ :view => view,
+ },
+ {
+ :view => { :kind_of => String }
+ }
+ )
+ if inflate
+ @rest.get_rest("#{Chef::Config[:couchdb_database]}/_view/#{view}/all")
+ else
+ @rest.get_rest("#{Chef::Config[:couchdb_database]}/_view/#{view}/all_id")
+ end
+ end
+
+ def has_key?(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ begin
+ @rest.get_rest("#{Chef::Config[:couchdb_database]}/#{obj_type}_#{safe_name(name)}")
+ true
+ rescue
+ false
+ end
+ end
+
+ private
+ def safe_name(name)
+ name.gsub(/\./, "_")
+ end
+
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb
new file mode 100644
index 0000000000..fa037a7f6c
--- /dev/null
+++ b/chef/lib/chef/exceptions.rb
@@ -0,0 +1,30 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ class Exception
+ class Exec < RuntimeError; end
+ class FileNotFound < RuntimeError; end
+ class Package < RuntimeError; end
+ class Service < RuntimeError; end
+ class SearchIndex < RuntimeError; end
+ class Override < RuntimeError; end
+ class UnsupportedAction < RuntimeError; end
+ class MissingLibrary < RuntimeError; end
+ class User < RuntimeError; end
+ end
+end
diff --git a/chef/lib/chef/file_cache.rb b/chef/lib/chef/file_cache.rb
new file mode 100644
index 0000000000..ea9e17fbb7
--- /dev/null
+++ b/chef/lib/chef/file_cache.rb
@@ -0,0 +1,203 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require File.join(File.dirname(__FILE__), "mixin", "create_path")
+require 'json'
+
+class Chef
+ class FileCache
+ class << self
+ include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::CreatePath
+
+ # Write a file to the File Cache.
+ #
+ # === Parameters
+ # path<String>:: The path to the file you want to put in the cache - should
+ # be relative to Chef::Config[:file_cache_path]
+ # contents<String>:: A string with the contents you want written to the file
+ #
+ # === Returns
+ # true
+ def store(path, contents)
+ validate(
+ {
+ :path => path,
+ :contents => contents
+ },
+ {
+ :path => { :kind_of => String },
+ :contents => { :kind_of => String },
+ }
+ )
+
+ file_path_array = File.split(path)
+ file_name = file_path_array.pop
+ cache_path = create_cache_path(File.join(file_path_array))
+ io = File.open(File.join(cache_path, file_name), "w")
+ io.print(contents)
+ io.close
+ true
+ end
+
+ # Move a file in to the cache. Useful with the REST raw file output.
+ #
+ # === Parameteres
+ # file<String>:: The path to the file you want in the cache
+ # path<String>:: The relative name you want the new file to use
+ def move_to(file, path)
+ validate(
+ {
+ :file => file,
+ :path => path
+ },
+ {
+ :file => { :kind_of => String },
+ :path => { :kind_of => String },
+ }
+ )
+
+ file_path_array = File.split(path)
+ file_name = file_path_array.pop
+ if File.exists?(file) && File.writable?(file)
+ File.rename(
+ file,
+ File.join(create_cache_path(File.join(file_path_array), true), file_name)
+ )
+ else
+ raise RuntimeError, "Cannot move #{file} to #{path}!"
+ end
+ end
+
+ # Read a file from the File Cache
+ #
+ # === Parameters
+ # path<String>:: The path to the file you want to load - should
+ # be relative to Chef::Config[:file_cache_path]
+ # read<True/False>:: Whether to return the file contents, or the path.
+ # Defaults to true.
+ #
+ # === Returns
+ # String:: A string with the file contents.
+ #
+ # === Raises
+ # Chef::Exception::FileNotFound:: If it cannot find the file in the cache
+ def load(path, read=true)
+ validate(
+ {
+ :path => path
+ },
+ {
+ :path => { :kind_of => String }
+ }
+ )
+ cache_path = create_cache_path(path, false)
+ raise Chef::Exception::FileNotFound, "Cannot find #{cache_path} for #{path}!" unless File.exists?(cache_path)
+ if read
+ File.read(cache_path)
+ else
+ cache_path
+ end
+ end
+
+ # Delete a file from the File Cache
+ #
+ # === Parameters
+ # path<String>:: The path to the file you want to delete - should
+ # be relative to Chef::Config[:file_cache_path]
+ #
+ # === Returns
+ # true
+ def delete(path)
+ validate(
+ {
+ :path => path
+ },
+ {
+ :path => { :kind_of => String },
+ }
+ )
+ cache_path = create_cache_path(path, false)
+ if File.exists?(cache_path)
+ File.unlink(cache_path)
+ end
+ true
+ end
+
+ # List all the files in the Cache
+ #
+ # === Returns
+ # Array:: An array of files in the cache, suitable for use with load, delete and store
+ def list()
+ keys = Array.new
+ Dir[File.join(Chef::Config[:file_cache_path], '**', '*')].each do |f|
+ if File.file?(f)
+ path = f.match("^#{Chef::Config[:file_cache_path]}\/(.+)")[1]
+ keys << path
+ end
+ end
+ keys
+ end
+
+ # Whether or not this file exists in the Cache
+ #
+ # === Parameters
+ # path:: The path to the file you want to check - is relative
+ # to Chef::Config[:file_cache_path]
+ #
+ # === Returns
+ # True:: If the file exists
+ # False:: If it does not
+ def has_key?(path)
+ validate(
+ {
+ :path => path
+ },
+ {
+ :path => { :kind_of => String },
+ }
+ )
+ full_path = create_cache_path(path, false)
+ if File.exists?(full_path)
+ true
+ else
+ false
+ end
+ end
+
+ # Create a full path to a given file in the cache. By default,
+ # also creates the path if it does not exist.
+ #
+ # === Parameters
+ # path:: The path to create, relative to Chef::Config[:file_cache_path]
+ # create_if_missing:: True by default - whether to create the path if it does not exist
+ #
+ # === Returns
+ # String:: The fully expanded path
+ def create_cache_path(path, create_if_missing=true)
+ cache_dir = File.expand_path(File.join(Chef::Config[:file_cache_path], path))
+ if create_if_missing
+ create_path(cache_dir)
+ else
+ cache_dir
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/file_store.rb b/chef/lib/chef/file_store.rb
new file mode 100644
index 0000000000..6fe13e5ad6
--- /dev/null
+++ b/chef/lib/chef/file_store.rb
@@ -0,0 +1,135 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require File.join(File.dirname(__FILE__), "mixin", "create_path")
+require 'digest/sha2'
+require 'json'
+
+class Chef
+ class FileStore
+ class << self
+ include Chef::Mixin::ParamsValidate
+ include Chef::Mixin::CreatePath
+
+ def store(obj_type, name, object)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ :object => object,
+ },
+ {
+ :object => { :respond_to => :to_json },
+ }
+ )
+
+ store_path = create_store_path(obj_type, name)
+ io = File.open(store_path, "w")
+ io.puts object.to_json
+ io.close
+ end
+
+ def load(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ store_path = create_store_path(obj_type, name)
+ raise "Cannot find #{store_path} for #{obj_type} #{name}!" unless File.exists?(store_path)
+ object = JSON.parse(IO.read(store_path))
+ end
+
+ def delete(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ store_path = create_store_path(obj_type, name)
+ if File.exists?(store_path)
+ File.unlink(store_path)
+ end
+ end
+
+ def list(obj_type, inflate=false)
+ validate(
+ {
+ :obj_type => obj_type,
+ },
+ {
+ :obj_type => { :kind_of => String }
+ }
+ )
+ keys = Array.new
+ Dir[File.join(Chef::Config[:file_store_path], obj_type, '**', '*')].each do |f|
+ if File.file?(f)
+ if inflate
+ keys << load(obj_type, File.basename(f))
+ else
+ keys << File.basename(f)
+ end
+ end
+ end
+ keys
+ end
+
+ def has_key?(obj_type, name)
+ validate(
+ {
+ :obj_type => obj_type,
+ :name => name,
+ },
+ {
+ :obj_type => { :kind_of => String },
+ :name => { :kind_of => String },
+ }
+ )
+ Dir[File.join(Chef::Config[:file_store_path], obj_type, '**', '*')].each do |f|
+ if File.file?(f)
+ return true if File.basename(f) == name
+ end
+ end
+ return false
+ end
+
+ def create_store_path(obj_type, key)
+ shadigest = Digest::SHA2.hexdigest("#{obj_type}#{key}")
+
+ file_path = [
+ Chef::Config[:file_store_path],
+ obj_type,
+ shadigest[0,1],
+ shadigest[1,3]
+ ]
+ File.join(create_path(file_path), key)
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/log.rb b/chef/lib/chef/log.rb
new file mode 100644
index 0000000000..ff98cc9dff
--- /dev/null
+++ b/chef/lib/chef/log.rb
@@ -0,0 +1,86 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'logger'
+
+class Chef
+ class Log
+
+ @logger = nil
+
+ class << self
+ attr_reader :logger #:nodoc
+
+ # Use Chef::Logger.init when you want to set up the logger manually. Arguments to this method
+ # get passed directly to Logger.new, so check out the documentation for the standard Logger class
+ # to understand what to do here.
+ #
+ # If this method is called with no arguments, it will log to STDOUT at the :info level.
+ #
+ # It also configures the Logger instance it creates to use the custom Chef::Log::Formatter class.
+ def init(*opts)
+ if opts.length == 0
+ @logger = Logger.new(STDOUT)
+ else
+ @logger = Logger.new(*opts)
+ end
+ @logger.formatter = Chef::Log::Formatter.new()
+ level(Chef::Config.log_level)
+ end
+
+ # Sets the level for the Logger object by symbol. Valid arguments are:
+ #
+ # :debug
+ # :info
+ # :warn
+ # :error
+ # :fatal
+ #
+ # Throws an ArgumentError if you feed it a bogus log level.
+ def level(loglevel)
+ init() unless @logger
+ case loglevel
+ when :debug
+ @logger.level = Logger::DEBUG
+ when :info
+ @logger.level = Logger::INFO
+ when :warn
+ @logger.level = Logger::WARN
+ when :error
+ @logger.level = Logger::ERROR
+ when :fatal
+ @logger.level = Logger::FATAL
+ else
+ raise ArgumentError, "Log level must be one of :debug, :info, :warn, :error, or :fatal"
+ end
+ end
+
+ # Passes any other method calls on directly to the underlying Logger object created with init. If
+ # this method gets hit before a call to Chef::Logger.init has been made, it will call
+ # Chef::Logger.init() with no arguments.
+ def method_missing(method_symbol, *args)
+ init() unless @logger
+ if args.length > 0
+ @logger.send(method_symbol, *args)
+ else
+ @logger.send(method_symbol)
+ end
+ end
+
+ end # class << self
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/log/formatter.rb b/chef/lib/chef/log/formatter.rb
new file mode 100644
index 0000000000..9f758178f5
--- /dev/null
+++ b/chef/lib/chef/log/formatter.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'logger'
+require 'time'
+
+class Chef
+ class Log
+ class Formatter < Logger::Formatter
+ @@show_time = true
+
+ def self.show_time=(show=false)
+ @@show_time = show
+ end
+
+ # Prints a log message as '[time] severity: message' if Chef::Log::Formatter.show_time == true.
+ # Otherwise, doesn't print the time.
+ def call(severity, time, progname, msg)
+ if @@show_time
+ sprintf("[%s] %s: %s\n", time.rfc2822(), severity, msg2str(msg))
+ else
+ sprintf("%s: %s\n", severity, msg2str(msg))
+ end
+ end
+
+ # Converts some argument to a Logger.severity() call to a string. Regular strings pass through like
+ # normal, Exceptions get formatted as "message (class)\nbacktrace", and other random stuff gets
+ # put through "object.inspect"
+ def msg2str(msg)
+ case msg
+ when ::String
+ msg
+ when ::Exception
+ "#{ msg.message } (#{ msg.class })\n" <<
+ (msg.backtrace || []).join("\n")
+ else
+ msg.inspect
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/mixin/check_helper.rb b/chef/lib/chef/mixin/check_helper.rb
new file mode 100644
index 0000000000..959b57cbb7
--- /dev/null
+++ b/chef/lib/chef/mixin/check_helper.rb
@@ -0,0 +1,31 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ module Mixin
+ module CheckHelper
+ def set_if_args(thing, arguments)
+ raise ArgumentError, "Must call set_if_args with a block!" unless Kernel.block_given?
+ if arguments != nil
+ yield(arguments)
+ else
+ thing
+ end
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/mixin/checksum.rb b/chef/lib/chef/mixin/checksum.rb
new file mode 100644
index 0000000000..5a7eed4165
--- /dev/null
+++ b/chef/lib/chef/mixin/checksum.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'digest/md5'
+
+class Chef
+ module Mixin
+ module Checksum
+
+ def checksum(file)
+ digest = Digest::MD5.new
+ fh = ::File.open(file)
+ fh.each do |line|
+ digest.update(line)
+ end
+ digest.hexdigest
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/mixin/command.rb b/chef/lib/chef/mixin/command.rb
new file mode 100644
index 0000000000..08344148a5
--- /dev/null
+++ b/chef/lib/chef/mixin/command.rb
@@ -0,0 +1,206 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'tmpdir'
+require 'fcntl'
+require 'etc'
+
+class Chef
+ module Mixin
+ module Command
+
+ def run_command(args={})
+ if args.has_key?(:creates)
+ if File.exists?(args[:creates])
+ Chef::Log.debug("Skipping #{args[:command_string]} - creates #{args[:creates]} exists.")
+ return false
+ end
+ end
+
+ if args.has_key?(:onlyif)
+ status = popen4(args[:onlyif]) { |p, i, o, e| }
+ if status.exitstatus != 0
+ Chef::Log.debug("Skipping #{args[:command_string]} - onlyif #{args[:onlyif]} returned #{status.exitstatus}")
+ return false
+ end
+ end
+
+ if args.has_key?(:not_if)
+ status = popen4(args[:not_if]) { |p, i, o, e| }
+ if status.exitstatus == 0
+ Chef::Log.debug("Skipping #{args[:command_string]} - unless #{args[:not_if]} returned #{status.exitstatus}")
+ return false
+ end
+ end
+
+ exec_processing_block = lambda do |pid, stdin, stdout, stderr|
+ stdin.close
+
+ stdout_string = stdout.gets(nil)
+ if stdout_string
+ Chef::Log.debug("---- Begin #{args[:command_string]} STDOUT ----")
+ Chef::Log.debug(stdout_string.strip)
+ Chef::Log.debug("---- End #{args[:command_string]} STDOUT ----")
+ end
+ stderr_string = stderr.gets(nil)
+ if stderr_string
+ Chef::Log.debug("---- Begin #{args[:command_string]} STDERR ----")
+ Chef::Log.debug(stderr_string.strip)
+ Chef::Log.debug("---- End #{args[:command_string]} STDERR ----")
+ end
+ end
+
+ args[:cwd] ||= Dir.tmpdir
+ unless File.directory?(args[:cwd])
+ raise Chef::Exception::Exec, "#{args[:cwd]} does not exist or is not a directory"
+ end
+
+ status = nil
+ Dir.chdir(args[:cwd]) do
+ if args[:timeout]
+ begin
+ Timeout.timeout(args[:timeout]) do
+ status = popen4(args[:command], args, &exec_processing_block)
+ end
+ rescue Exception => e
+ Chef::Log.error("#{args[:command_string]} exceeded timeout #{args[:timeout]}")
+ raise(e)
+ end
+ else
+ status = popen4(args[:command], args, &exec_processing_block)
+ end
+
+ args[:returns] ||= 0
+ if status.exitstatus != args[:returns]
+ raise Chef::Exception::Exec, "#{args[:command_string]} returned #{status.exitstatus}, expected #{args[:returns]}"
+ else
+ Chef::Log.debug("Ran #{args[:command_string]} (#{args[:command]}) returned #{status.exitstatus}")
+ end
+ end
+ status
+ end
+
+ module_function :run_command
+
+ # This is taken directly from Ara T Howard's Open4 library, and then
+ # modified to suit the needs of Chef. Any bugs here are most likely
+ # my own, and not Ara's.
+ #
+ # The original appears in external/open4.rb in it's unmodified form.
+ #
+ # Thanks, Ara.
+ def popen4(cmd, args={}, &b)
+
+ args[:user] ||= nil
+ unless args[:user].kind_of?(Integer)
+ args[:user] = Etc.getpwnam(args[:user]).uid if args[:user]
+ end
+ args[:group] ||= nil
+ unless args[:group].kind_of?(Integer)
+ args[:group] = Etc.getgrnam(args[:group]).gid if args[:group]
+ end
+ args[:environment] ||= nil
+
+ pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe
+
+ verbose = $VERBOSE
+ begin
+ $VERBOSE = nil
+ ps.last.fcntl(Fcntl::F_SETFD, Fcntl::FD_CLOEXEC)
+
+ cid = fork {
+ pw.last.close
+ STDIN.reopen pw.first
+ pw.first.close
+
+ pr.first.close
+ STDOUT.reopen pr.last
+ pr.last.close
+
+ pe.first.close
+ STDERR.reopen pe.last
+ pe.last.close
+
+ STDOUT.sync = STDERR.sync = true
+
+ if args[:user]
+ Process.euid = args[:user]
+ Process.uid = args[:user]
+ end
+
+ if args[:group]
+ Process.egid = args[:group]
+ Process.gid = args[:group]
+ end
+
+ if args[:environment]
+ args[:environment].each do |key,value|
+ ENV[key] = value
+ end
+ end
+
+ begin
+ if cmd.kind_of?(Array)
+ exec(*cmd)
+ else
+ exec(cmd)
+ end
+ raise 'forty-two'
+ rescue Exception => e
+ Marshal.dump(e, ps.last)
+ ps.last.flush
+ end
+ ps.last.close unless (ps.last.closed?)
+ exit!
+ }
+ ensure
+ $VERBOSE = verbose
+ end
+
+ [pw.first, pr.last, pe.last, ps.last].each{|fd| fd.close}
+
+ begin
+ e = Marshal.load ps.first
+ raise(Exception === e ? e : "unknown failure!")
+ rescue EOFError # If we get an EOF error, then the exec was successful
+ 42
+ ensure
+ ps.first.close
+ end
+
+ pw.last.sync = true
+
+ pi = [pw.last, pr.first, pe.first]
+
+ if b
+ begin
+ b[cid, *pi]
+ Process.waitpid2(cid).last
+ ensure
+ pi.each{|fd| fd.close unless fd.closed?}
+ end
+ else
+ [cid, pw.last, pr.first, pe.first]
+ end
+ end
+
+ module_function :popen4
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/mixin/create_path.rb b/chef/lib/chef/mixin/create_path.rb
new file mode 100644
index 0000000000..dc9200eaf5
--- /dev/null
+++ b/chef/lib/chef/mixin/create_path.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ module Mixin
+ module CreatePath
+
+ # Creates a given path, including all directories that lead up to it.
+ # Like mkdir_p, but without the leaking.
+ #
+ # === Parameters
+ # file_path<String, Array>:: A string that represents the path to create,
+ # or an Array with the path-parts.
+ #
+ # === Returns
+ # The created file_path.
+ def create_path(file_path)
+ unless file_path.kind_of?(String) || file_path.kind_of?(Array)
+ raise ArgumentError, "file_path must be a string or an array!"
+ end
+
+ if file_path.kind_of?(String)
+ file_path = File.expand_path(file_path).split(File::SEPARATOR)
+ file_path.shift if file_path[0] = ''
+ unless file_path[0].match("^#{File::SEPARATOR}")
+ file_path[0] = "#{File::SEPARATOR}#{file_path[0]}"
+ end
+ end
+
+ file_path.each_index do |i|
+ create_path = File.join(file_path[0, i + 1])
+ unless File.directory?(create_path)
+ Chef::Log.debug("Creating directory #{create_path}")
+ Dir.mkdir(create_path)
+ end
+ end
+ File.expand_path(File.join(file_path))
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/mixin/from_file.rb b/chef/lib/chef/mixin/from_file.rb
new file mode 100644
index 0000000000..05f7fe8e8b
--- /dev/null
+++ b/chef/lib/chef/mixin/from_file.rb
@@ -0,0 +1,36 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ module Mixin
+ module FromFile
+
+ # Loads a given ruby file, and runs instance_eval against it in the context of the current
+ # object.
+ #
+ # Raises an IOError if the file cannot be found, or is not readable.
+ def from_file(filename)
+ if File.exists?(filename) && File.readable?(filename)
+ self.instance_eval(IO.read(filename), filename, 1)
+ else
+ raise IOError, "Cannot open or read #{filename}!"
+ end
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/mixin/generate_url.rb b/chef/lib/chef/mixin/generate_url.rb
new file mode 100644
index 0000000000..3eb8c4f70d
--- /dev/null
+++ b/chef/lib/chef/mixin/generate_url.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ module Mixin
+ module GenerateURL
+
+ def generate_cookbook_url(url, cookbook, type, node, args=nil)
+ new_url = nil
+ if url =~ /^http/
+ new_url = url
+ else
+ new_url = "cookbooks/#{cookbook}/#{type}?"
+ new_url += "id=#{url}"
+ platform, version = Chef::Platform.find_platform_and_version(node)
+ if type == "files" || type == "templates"
+ new_url += "&platform=#{platform}&version=#{version}&fqdn=#{node[:fqdn]}"
+ end
+ if args
+ args.each do |key, value|
+ new_url += "&#{key}=#{value}"
+ end
+ end
+ end
+
+ return new_url
+ end
+
+ end
+ end
+end
diff --git a/chef/lib/chef/mixin/params_validate.rb b/chef/lib/chef/mixin/params_validate.rb
new file mode 100644
index 0000000000..4d8ddb74c3
--- /dev/null
+++ b/chef/lib/chef/mixin/params_validate.rb
@@ -0,0 +1,197 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+
+class Chef
+ module Mixin
+ module ParamsValidate
+
+ # Takes a hash of options, along with a map to validate them. Returns the original
+ # options hash, plus any changes that might have been made (through things like setting
+ # default values in the validation map)
+ #
+ # For example:
+ #
+ # validate({ :one => "neat" }, { :one => { :kind_of => String }})
+ #
+ # Would raise an exception if the value of :one above is not a kind_of? string. Valid
+ # map options are:
+ #
+ # :default:: Sets the default value for this parameter.
+ # :callbacks:: Takes a hash of Procs, which should return true if the argument is valid.
+ # The key will be inserted into the error message if the Proc does not return true:
+ # "Option #{key}'s value #{value} #{message}!"
+ # :kind_of:: Ensure that the value is a kind_of?(Whatever). If passed an array, it will ensure
+ # that the value is one of those types.
+ # :respond_to:: Ensure that the value has a given method. Takes one method name or an array of
+ # method names.
+ # :required:: Raise an exception if this parameter is missing. Valid values are true or false,
+ # by default, options are not required.
+ # :regex:: Match the value of the paramater against a regular expression.
+ # :equal_to:: Match the value of the paramater with ==. An array means it can be equal to any
+ # of the values.
+ def validate(opts, map)
+ #--
+ # validate works by taking the keys in the validation map, assuming it's a hash, and
+ # looking for _pv_:symbol as methods. Assuming it find them, it calls the right
+ # one.
+ #++
+ raise ArgumentError, "Options must be a hash" unless opts.kind_of?(Hash)
+ raise ArgumentError, "Validation Map must be a hash" unless map.kind_of?(Hash)
+
+ map.each do |key, validation|
+ unless key.kind_of?(Symbol) || key.kind_of?(String)
+ raise ArgumentError, "Validation map keys must be symbols or strings!"
+ end
+ case validation
+ when true
+ _pv_required(opts, key)
+ when false
+ true
+ when Hash
+ validation.each do |check, carg|
+ check_method = "_pv_#{check.to_s}"
+ if self.respond_to?(check_method, true)
+ self.send(check_method, opts, key, carg)
+ else
+ raise ArgumentError, "Validation map has unknown check: #{check}"
+ end
+ end
+ end
+ end
+ opts
+ end
+
+ def set_or_return(symbol, arg, validation)
+ iv_symbol = "@#{symbol.to_s}".to_sym
+ map = {
+ symbol => validation
+ }
+ if arg == nil
+ self.instance_variable_get(iv_symbol)
+ else
+ validate({ symbol => arg }, { symbol => validation })
+ self.instance_variable_set(iv_symbol, arg)
+ end
+ end
+
+ private
+
+ # Return the value of a parameter, or nil if it doesn't exist.
+ def _pv_opts_lookup(opts, key)
+ if opts.has_key?(key.to_s)
+ opts[key.to_s]
+ elsif opts.has_key?(key.to_sym)
+ opts[key.to_sym]
+ else
+ nil
+ end
+ end
+
+ # Raise an exception if the parameter is not found.
+ def _pv_required(opts, key, is_required=true)
+ if is_required
+ if opts.has_key?(key.to_s) || opts.has_key?(key.to_sym)
+ true
+ else
+ raise ArgumentError, "Required argument #{key} is missing!"
+ end
+ end
+ end
+
+ def _pv_equal_to(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ if value != nil
+ passes = false
+ [ to_be ].flatten.each do |tb|
+ if value == tb
+ passes = true
+ end
+ end
+ unless passes
+ raise ArgumentError, "Option #{key} must be equal to one of: #{to_be.join(", ")}! You passed #{value.inspect}."
+ end
+ end
+ end
+
+ # Raise an exception if the parameter is not a kind_of?(to_be)
+ def _pv_kind_of(opts, key, to_be)
+ value = _pv_opts_lookup(opts, key)
+ if value != nil
+ passes = false
+ [ to_be ].flatten.each do |tb|
+ if value.kind_of?(tb)
+ passes = true
+ end
+ end
+ unless passes
+ raise ArgumentError, "Option #{key} must be a kind of #{to_be}! You passed #{value.inspect}."
+ end
+ end
+ end
+
+ # Raise an exception if the parameter does not respond to a given set of methods.
+ def _pv_respond_to(opts, key, method_name_list)
+ value = _pv_opts_lookup(opts, key)
+ if value != nil
+ [ method_name_list ].flatten.each do |method_name|
+ unless value.respond_to?(method_name)
+ raise ArgumentError, "Option #{key} must have a #{method_name} method!"
+ end
+ end
+ end
+ end
+
+ # Assign a default value to a parameter.
+ def _pv_default(opts, key, default_value)
+ value = _pv_opts_lookup(opts, key)
+ if value == nil
+ opts[key] = default_value
+ end
+ end
+
+ # Check a parameter against a regular expression.
+ def _pv_regex(opts, key, regex)
+ value = _pv_opts_lookup(opts, key)
+ passes = false
+ [ regex ].flatten.each do |r|
+ if value != nil
+ if r.match(value.to_s)
+ passes = true
+ end
+ end
+ end
+ unless passes
+ raise ArgumentError, "Option #{key}'s value #{value} does not match regular expression #{regex.to_s}"
+ end
+ end
+
+ # Check a parameter against a hash of proc's.
+ def _pv_callbacks(opts, key, callbacks)
+ raise ArgumentError, "Callback list must be a hash!" unless callbacks.kind_of?(Hash)
+ value = _pv_opts_lookup(opts, key)
+ if value != nil
+ callbacks.each do |message, zeproc|
+ if zeproc.call(value) != true
+ raise ArgumentError, "Option #{key}'s value #{value} #{message}!"
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
diff --git a/chef/lib/chef/mixin/template.rb b/chef/lib/chef/mixin/template.rb
new file mode 100644
index 0000000000..f3980922a1
--- /dev/null
+++ b/chef/lib/chef/mixin/template.rb
@@ -0,0 +1,39 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'tempfile'
+require 'erubis'
+
+class Chef
+ module Mixin
+ module Template
+
+ # Render a template with Erubis. Takes a template as a string, and a
+ # context hash.
+ def render_template(template, context)
+ eruby = Erubis::Eruby.new(template)
+ output = eruby.evaluate(context)
+ final_tempfile = Tempfile.new("chef-rendered-template")
+ final_tempfile.print(output)
+ final_tempfile.close
+ final_tempfile
+ end
+
+ end
+ end
+end
diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb
new file mode 100644
index 0000000000..e1c8ef2d9a
--- /dev/null
+++ b/chef/lib/chef/node.rb
@@ -0,0 +1,260 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "check_helper")
+require File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require File.join(File.dirname(__FILE__), "mixin", "from_file")
+
+require 'extlib'
+require 'rubygems'
+require 'json'
+
+class Chef
+ class Node
+
+ attr_accessor :attribute, :recipe_list, :couchdb_rev
+
+ include Chef::Mixin::CheckHelper
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+
+ DESIGN_DOCUMENT = {
+ "version" => 3,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "node") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ "all_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "node") {
+ emit(doc.name, doc.name);
+ }
+ }
+ EOJS
+ },
+ },
+ }
+
+ # Create a new Chef::Node object.
+ def initialize()
+ @name = nil
+ @attribute = Mash.new
+ @recipe_list = Array.new
+ @couchdb_rev = nil
+ @couchdb = Chef::CouchDB.new
+ end
+
+ # Find a recipe for this Chef::Node by fqdn. Will search first for
+ # Chef::Config["node_path"]/fqdn.rb, then hostname.rb, then default.rb.
+ #
+ # Returns a new Chef::Node object.
+ #
+ # Raises an ArgumentError if it cannot find the node.
+ def find_file(fqdn)
+ node_file = nil
+ host_parts = fqdn.split(".")
+ hostname = host_parts[0]
+
+ if File.exists?(File.join(Chef::Config[:node_path], "#{fqdn}.rb"))
+ node_file = File.join(Chef::Config[:node_path], "#{fqdn}.rb")
+ elsif File.exists?(File.join(Chef::Config[:node_path], "#{hostname}.rb"))
+ node_file = File.join(Chef::Config[:node_path], "#{hostname}.rb")
+ elsif File.exists?(File.join(Chef::Config[:node_path], "default.rb"))
+ node_file = File.join(Chef::Config[:node_path], "default.rb")
+ end
+ unless node_file
+ raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!"
+ end
+ self.from_file(node_file)
+ end
+
+ # Set the name of this Node, or return the current name.
+ def name(arg=nil)
+ if arg != nil
+ validate(
+ { :name => arg },
+ {
+ :name => {
+ :kind_of => String
+ }
+ }
+ )
+ @name = arg
+ else
+ @name
+ end
+ end
+
+ # Return an attribute of this node. Returns nil if the attribute is not found.
+ def [](attrib)
+ if @attribute.has_key?(attrib)
+ @attribute[attrib]
+ elsif @attribute.has_key?(attrib.to_s)
+ @attribute[attrib.to_s]
+ else
+ nil
+ end
+ end
+
+ # Set an attribute of this node
+ def []=(attrib, value)
+ @attribute[attrib] = value
+ end
+
+ # Yield each key to the block
+ def each(&block)
+ @attribute.each_key do |k|
+ yield(k)
+ end
+ end
+
+ # Iterates over each attribute, passing the attribute and value to the block.
+ def each_attribute(&block)
+ @attribute.each do |k,v|
+ yield(k, v)
+ end
+ end
+
+ # Return true if this Node has a given attribute, false if not. Takes either a symbol or
+ # a string.
+ def attribute?(attrib)
+ result = false
+ result = @attribute.has_key?(attrib)
+ return result if result
+ return @attribute.has_key?(attrib.to_sym)
+ end
+
+ # Returns true if this Node expects a given recipe, false if not.
+ def recipe?(recipe_name)
+ @recipe_list.detect { |r| r == recipe_name } ? true : false
+ end
+
+ # Returns an Array of recipes. If you call it with arguments, they will become the new
+ # list of recipes.
+ def recipes(*args)
+ if args.length > 0
+ @recipe_list = args.flatten
+ else
+ @recipe_list
+ end
+ end
+
+ # Set an attribute based on the missing method. If you pass an argument, we'll use that
+ # to set the attribute values. Otherwise, we'll wind up just returning the attributes
+ # value.
+ def method_missing(symbol, *args)
+ if args.length != 0
+ @attribute[symbol] = args.length == 1 ? args[0] : args
+ else
+ if @attribute.has_key?(symbol)
+ @attribute[symbol]
+ else
+ raise ArgumentError, "Attribute #{symbol.to_s} is not defined!"
+ end
+ end
+ end
+
+ def to_index
+ index_hash = {
+ :index_name => "node",
+ :id => "node_#{@name}",
+ :name => @name,
+ }
+ @attribute.each do |key, value|
+ index_hash[key] = value
+ end
+ index_hash[:recipe] = @recipe_list if @recipe_list.length > 0
+ index_hash
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ result = {
+ "name" => @name,
+ 'json_class' => self.class.name,
+ "attributes" => @attribute,
+ "chef_type" => "node",
+ "recipes" => @recipe_list,
+ }
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result.to_json(*a)
+ end
+
+ # Create a Chef::Node from JSON
+ def self.json_create(o)
+ node = new
+ node.name(o["name"])
+ o["attributes"].each do |k,v|
+ node[k] = v
+ end
+ o["recipes"].each do |r|
+ node.recipes << r
+ end
+ node.couchdb_rev = o["_rev"] if o.has_key?("_rev")
+ node
+ end
+
+ # List all the Chef::Node objects in the CouchDB. If inflate is set to true, you will get
+ # the full list of all Nodes, fully inflated.
+ def self.list(inflate=false)
+ rs = Chef::CouchDB.new.list("nodes", inflate)
+ if inflate
+ rs["rows"].collect { |r| r["value"] }
+ else
+ rs["rows"].collect { |r| r["key"] }
+ end
+ end
+
+ # Load a node by name from CouchDB
+ def self.load(name)
+ Chef::CouchDB.new.load("node", name)
+ end
+
+ # Remove this node from the CouchDB
+ def destroy
+ Chef::Queue.send_msg(:queue, :remove, self)
+ @couchdb.delete("node", @name, @couchdb_rev)
+ end
+
+ # Save this node to the CouchDB
+ def save
+ Chef::Queue.send_msg(:queue, :index, self)
+ results = @couchdb.store("node", @name, self)
+ @couchdb_rev = results["rev"]
+ end
+
+ # Set up our CouchDB design document
+ def self.create_design_document
+ Chef::CouchDB.new.create_design_document("nodes", DESIGN_DOCUMENT)
+ end
+
+ # As a string
+ def to_s
+ "node[#{@name}]"
+ end
+
+ end
+end
diff --git a/chef/lib/chef/openid_registration.rb b/chef/lib/chef/openid_registration.rb
new file mode 100644
index 0000000000..748c865097
--- /dev/null
+++ b/chef/lib/chef/openid_registration.rb
@@ -0,0 +1,175 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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'
+require 'json'
+
+class Chef
+ class OpenIDRegistration
+
+ attr_accessor :name, :salt, :validated, :password, :couchdb_rev
+
+ include Chef::Mixin::ParamsValidate
+
+ DESIGN_DOCUMENT = {
+ "version" => 3,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ "all_id" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ emit(doc.name, doc.name);
+ }
+ }
+ EOJS
+ },
+ "validated" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ if (doc.validated == true) {
+ emit(doc.name, doc);
+ }
+ }
+ }
+ EOJS
+ },
+ "unvalidated" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "openid_registration") {
+ if (doc.validated == false) {
+ emit(doc.name, doc);
+ }
+ }
+ }
+ EOJS
+ },
+ },
+ }
+
+ # Create a new Chef::OpenIDRegistration object.
+ def initialize()
+ @name = nil
+ @salt = nil
+ @password = nil
+ @validated = false
+ @couchdb_rev = nil
+ @couchdb = Chef::CouchDB.new
+ end
+
+ def name=(n)
+ @name = n.gsub(/\./, '_')
+ end
+
+ # Set the password for this object.
+ def set_password(password)
+ @salt = generate_salt
+ @password = encrypt_password(@salt, password)
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ attributes = Hash.new
+ recipes = Array.new
+ result = {
+ 'name' => @name,
+ 'json_class' => self.class.name,
+ 'salt' => @salt,
+ 'password' => @password,
+ 'validated' => @validated,
+ 'chef_type' => 'openid_registration',
+ }
+ result["_rev"] = @couchdb_rev if @couchdb_rev
+ result.to_json(*a)
+ end
+
+ # Create a Chef::Node from JSON
+ def self.json_create(o)
+ me = new
+ me.name = o["name"]
+ me.salt = o["salt"]
+ me.password = o["password"]
+ me.validated = o["validated"]
+ me.couchdb_rev = o["_rev"] if o.has_key?("_rev")
+ me
+ end
+
+ # List all the Chef::OpenIDRegistration objects in the CouchDB. If inflate is set to true, you will get
+ # the full list of all registration objects. Otherwise, you'll just get the IDs
+ def self.list(inflate=false)
+ rs = Chef::CouchDB.new.list("registrations", inflate)
+ if inflate
+ rs["rows"].collect { |r| r["value"] }
+ else
+ rs["rows"].collect { |r| r["key"] }
+ end
+ end
+
+ # Load an OpenIDRegistration by name from CouchDB
+ def self.load(name)
+ Chef::CouchDB.new.load("openid_registration", name)
+ end
+
+ # Whether or not there is an OpenID Registration with this key.
+ def self.has_key?(name)
+ Chef::CouchDB.new.has_key?("openid_registration", name)
+ end
+
+ # Remove this node from the CouchDB
+ def destroy
+ @couchdb.delete("openid_registration", @name, @couchdb_rev)
+ end
+
+ # Save this node to the CouchDB
+ def save
+ results = @couchdb.store("openid_registration", @name, self)
+ @couchdb_rev = results["rev"]
+ end
+
+ # Set up our CouchDB design document
+ def self.create_design_document
+ Chef::CouchDB.new.create_design_document("registrations", DESIGN_DOCUMENT)
+ end
+
+ protected
+
+ def generate_salt
+ salt = Time.now.to_s
+ chars = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a
+ 1.upto(30) { |i| salt << chars[rand(chars.size-1)] }
+ salt
+ end
+
+ def encrypt_password(salt, password)
+ Digest::SHA1.hexdigest("--#{salt}--#{password}--")
+ end
+
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/platform.rb b/chef/lib/chef/platform.rb
new file mode 100644
index 0000000000..c7a7be1236
--- /dev/null
+++ b/chef/lib/chef/platform.rb
@@ -0,0 +1,202 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+Dir[File.join(File.dirname(__FILE__), 'provider/**/*.rb')].sort.each { |lib| require lib }
+require File.join(File.dirname(__FILE__), 'mixin', 'params_validate')
+
+class Chef
+ class Platform
+
+ @platforms = {
+ :mac_os_x => {},
+ :ubuntu => {
+ :default => {
+ :package => Chef::Provider::Package::Apt,
+ :service => Chef::Provider::Service::Debian,
+ }
+ },
+ :centos => {},
+ :redhat => {},
+ :gentoo => {
+ :default => {
+ :package => Chef::Provider::Package::Portage
+ }
+ },
+ :solaris => {},
+ :default => {
+ :file => Chef::Provider::File,
+ :directory => Chef::Provider::Directory,
+ :link => Chef::Provider::Link,
+ :template => Chef::Provider::Template,
+ :remote_file => Chef::Provider::RemoteFile,
+ :remote_directory => Chef::Provider::RemoteDirectory,
+ :sysctl => Chef::Provider::Sysctl,
+ :execute => Chef::Provider::Execute,
+ :script => Chef::Provider::Script,
+ :service => Chef::Provider::Service::Init,
+ :perl => Chef::Provider::Script,
+ :python => Chef::Provider::Script,
+ :ruby => Chef::Provider::Script,
+ :bash => Chef::Provider::Script,
+ :csh => Chef::Provider::Script,
+ :user => Chef::Provider::User::Useradd,
+ }
+ }
+
+ class << self
+ attr_accessor :platforms
+
+ include Chef::Mixin::ParamsValidate
+
+ def find(name, version)
+ provider_map = @platforms[:default].clone
+
+ name_sym = name
+ if name.kind_of?(String)
+ name.downcase!
+ name.gsub!(/\s/, "_")
+ name_sym = name.to_sym
+ end
+
+ if @platforms.has_key?(name_sym)
+ if @platforms[name_sym].has_key?(version)
+ Chef::Log.debug("Platform #{name.to_s} version #{version} found")
+ if @platforms[name_sym].has_key?(:default)
+ provider_map.merge!(@platforms[name_sym][:default])
+ end
+ provider_map.merge!(@platforms[name_sym][version])
+ elsif @platforms[name_sym].has_key?(:default)
+ provider_map.merge!(@platforms[name_sym][:default])
+ end
+ else
+ Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)")
+ end
+ provider_map
+ end
+
+ def find_provider(platform, version, resource_type)
+ pmap = Chef::Platform.find(platform, version)
+ rtkey = resource_type
+ if resource_type.kind_of?(Chef::Resource)
+ rtkey = resource_type.resource_name.to_sym
+ end
+ if pmap.has_key?(rtkey)
+ pmap[rtkey]
+ else
+ Chef::Log.error("#{rtkey.inspect} #{pmap.inspect}")
+ raise(
+ ArgumentError,
+ "Cannot find a provider for #{resource_type} on #{platform} version #{version}"
+ )
+ end
+ end
+
+ def find_platform_and_version(node)
+ platform = nil
+ version = nil
+ if node.attribute?("lsbdistid")
+ platform = node[:lsbdistid]
+ elsif node.attribute?("macosx_productname")
+ platform = node[:macosx_productname]
+ elsif node.attribute?("operatingsystem")
+ platform = node[:operatingsystem]
+ end
+ raise ArgumentError, "Cannot find a platform for #{node}" unless platform
+
+ if node.attribute?("lsbdistrelease")
+ version = node[:lsbdistrelease]
+ elsif node.attribute?("macosx_productversion")
+ version = node[:macosx_productversion]
+ elsif node.attribute?("operatingsystemversion")
+ version = node[:operatingsystemversion]
+ elsif node.attribute?("operatingsystemrelease")
+ version = node[:operatingsystemrelease]
+ end
+ raise ArgumentError, "Cannot find a version for #{node}" unless version
+
+ return platform, version
+ end
+
+ def find_provider_for_node(node, resource_type)
+ platform, version = find_platform_and_version(node)
+ provider = find_provider(platform, version, resource_type)
+ end
+
+ def set(args)
+ validate(
+ args,
+ {
+ :platform => {
+ :kind_of => Symbol,
+ :required => false,
+ },
+ :version => {
+ :kind_of => String,
+ :required => false,
+ },
+ :resource => {
+ :kind_of => Symbol,
+ },
+ :provider => {
+ :kind_of => [ String, Symbol, Class ],
+ }
+ }
+ )
+ if args.has_key?(:platform)
+ if args.has_key?(:version)
+ if @platforms.has_key?(args[:platform])
+ if @platforms[args[:platform]].has_key?(args[:version])
+ @platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider]
+ else
+ @platforms[args[:platform]][args[:version]] = {
+ args[:resource].to_sym => args[:provider]
+ }
+ end
+ else
+ @platforms[args[:platform]] = {
+ args[:version] => {
+ args[:resource].to_sym => args[:provider]
+ }
+ }
+ end
+ else
+ if @platforms.has_key?(args[:platform])
+ @platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider]
+ else
+ @platforms[args[:platform]] = {
+ :default => {
+ args[:resource].to_sym => args[:provider]
+ }
+ }
+ end
+ end
+ else
+ if @platforms.has_key?(:default)
+ @platforms[:default][args[:resource].to_sym] = args[:provider]
+ else
+ @platforms[:default] = {
+ args[:resource].to_sym => args[:provider]
+ }
+ end
+ end
+ end
+
+ end
+
+ end
+end
diff --git a/chef/lib/chef/provider.rb b/chef/lib/chef/provider.rb
new file mode 100644
index 0000000000..3251693cb3
--- /dev/null
+++ b/chef/lib/chef/provider.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Provider
+
+ attr_accessor :node, :new_resource, :current_resource
+
+ def initialize(node, new_resource)
+ @node = node
+ @new_resource = new_resource
+ @current_resource = nil
+ end
+
+ def load_current_resource
+ raise Chef::Exception::Override, "You must override load_current_resource in #{self.to_s}"
+ end
+
+ def action_nothing
+ Chef::Log.debug("Doing nothing for #{@new_resource.to_s}")
+ true
+ end
+
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/directory.rb b/chef/lib/chef/provider/directory.rb
new file mode 100644
index 0000000000..d042b6a8eb
--- /dev/null
+++ b/chef/lib/chef/provider/directory.rb
@@ -0,0 +1,68 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "file")
+require "fileutils"
+
+class Chef
+ class Provider
+ class Directory < Chef::Provider::File
+ def load_current_resource
+ @current_resource = Chef::Resource::Directory.new(@new_resource.name)
+ @current_resource.path(@new_resource.path)
+ if ::File.exist?(@current_resource.path) && ::File.directory?(@current_resource.path)
+ cstats = ::File.stat(@current_resource.path)
+ @current_resource.owner(cstats.uid)
+ @current_resource.group(cstats.gid)
+ @current_resource.mode("%o" % (cstats.mode & 007777))
+ end
+ @current_resource
+ end
+
+ def action_create
+ unless ::File.exists?(@new_resource.path)
+ Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}")
+ if @new_resource.recursive == true
+ ::FileUtils.mkdir_p(@new_resource.path)
+ else
+ ::Dir.mkdir(@new_resource.path)
+ end
+ @new_resource.updated = true
+ end
+ set_owner if @new_resource.owner != nil
+ set_group if @new_resource.group != nil
+ set_mode if @new_resource.mode != nil
+ end
+
+ def action_delete
+ if ::File.exists?(@new_resource.path) && ::File.writable?(@new_resource.path)
+ if @new_resource.recursive == true
+ Chef::Log.info("Deleting #{@new_resource} recursively at #{@new_resource.path}")
+ FileUtils.rm_rf(@new_resource.path)
+ else
+ Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.path}")
+ ::Dir.delete(@new_resource.path)
+ end
+ @new_resource.updated = true
+ else
+ raise RuntimeError, "Cannot delete #{@new_resource} at #{@new_resource_path}!" if ::File.exists?(@new_resource.path)
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/execute.rb b/chef/lib/chef/provider/execute.rb
new file mode 100644
index 0000000000..67a706e4f1
--- /dev/null
+++ b/chef/lib/chef/provider/execute.rb
@@ -0,0 +1,55 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Execute < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ def load_current_resource
+ true
+ end
+
+ def action_run
+ command_args = {
+ :command => @new_resource.command,
+ :command_string => @new_resource.to_s,
+ }
+ command_args[:creates] = @new_resource.creates if @new_resource.creates
+ command_args[:onlyif] = @new_resource.onlyif if @new_resource.onlyif
+ command_args[:not_if] = @new_resource.not_if if @new_resource.not_if
+ command_args[:timeout] = @new_resource.timeout if @new_resource.timeout
+ command_args[:returns] = @new_resource.returns if @new_resource.returns
+ command_args[:environment] = @new_resource.environment if @new_resource.environment
+ command_args[:user] = @new_resource.user if @new_resource.user
+ command_args[:group] = @new_resource.group if @new_resource.group
+ command_args[:cwd] = @new_resource.cwd if @new_resource.cwd
+
+ status = run_command(command_args)
+ if status
+ @new_resource.updated = true
+ Chef::Log.info("Ran #{@new_resource} successfully")
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/file.rb b/chef/lib/chef/provider/file.rb
new file mode 100644
index 0000000000..d25e5decca
--- /dev/null
+++ b/chef/lib/chef/provider/file.rb
@@ -0,0 +1,169 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'etc'
+require 'fileutils'
+require File.join(File.dirname(__FILE__), "..", "mixin", "checksum")
+require File.join(File.dirname(__FILE__), "..", "mixin", "generate_url")
+
+class Chef
+ class Provider
+ class File < Chef::Provider
+ include Chef::Mixin::Checksum
+ include Chef::Mixin::GenerateURL
+
+ def load_current_resource
+ @current_resource = Chef::Resource::File.new(@new_resource.name)
+ @current_resource.path(@new_resource.path)
+ if ::File.exist?(@current_resource.path) && ::File.readable?(@current_resource.path)
+ cstats = ::File.stat(@current_resource.path)
+ @current_resource.owner(cstats.uid)
+ @current_resource.group(cstats.gid)
+ @current_resource.mode("%o" % (cstats.mode & 007777))
+ @current_resource.checksum(checksum(@current_resource.path))
+ end
+ @current_resource
+ end
+
+ # Compare the ownership of a file. Returns true if they are the same, false if they are not.
+ def compare_owner
+ if @new_resource.owner != nil
+ case @new_resource.owner
+ when /^\d+$/, Integer
+ @set_user_id = @new_resource.owner.to_i
+ @set_user_id == @current_resource.owner
+ else
+ # This raises an ArugmentError if you can't find the user
+ user_info = Etc.getpwnam(@new_resource.owner)
+ @set_user_id = user_info.uid
+ @set_user_id == @current_resource.owner
+ end
+ end
+ end
+
+ # Set the ownership on the file, assuming it is not set correctly already.
+ def set_owner
+ unless compare_owner
+ Chef::Log.info("Setting owner to #{@set_user_id} for #{@new_resource}")
+ ::File.chown(@set_user_id, nil, @new_resource.path)
+ @new_resource.updated = true
+ end
+ end
+
+ # Compares the group of a file. Returns true if they are the same, false if they are not.
+ def compare_group
+ if @new_resource.group != nil
+ case @new_resource.group
+ when /^\d+$/, Integer
+ @set_group_id = @new_resource.group.to_i
+ @set_group_id == @current_resource.group
+ else
+ group_info = Etc.getgrnam(@new_resource.group)
+ @set_group_id = group_info.gid
+ @set_group_id == @current_resource.group
+ end
+ end
+ end
+
+ def set_group
+ unless compare_group
+ Chef::Log.info("Setting group to #{@set_group_id} for #{@new_resource}")
+ ::File.chown(nil, @set_group_id, @new_resource.path)
+ @new_resource.updated = true
+ end
+ end
+
+ def compare_mode
+ if @new_resource.mode != nil
+ case @new_resource.mode
+ when /^\d+$/, Integer
+ real_mode = sprintf("%o" % (@new_resource.mode & 007777))
+ real_mode.to_i == @current_resource.mode.to_i
+ end
+ end
+ end
+
+ def set_mode
+ unless compare_mode && @new_resource.mode != nil
+ Chef::Log.info("Setting mode to #{sprintf("%o" % (@new_resource.mode & 007777))
+ } for #{@new_resource}")
+ ::File.chmod(@new_resource.mode.to_i, @new_resource.path)
+ @new_resource.updated = true
+ end
+ end
+
+ def action_create
+ unless ::File.exists?(@new_resource.path)
+ Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}")
+ ::File.open(@new_resource.path, "w+") { |f| }
+ @new_resource.updated = true
+ end
+ set_owner if @new_resource.owner != nil
+ set_group if @new_resource.group != nil
+ set_mode if @new_resource.mode != nil
+ end
+
+ def action_delete
+ if ::File.exists?(@new_resource.path) && ::File.writable?(@new_resource.path)
+ backup
+ Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.path}")
+ ::File.delete(@new_resource.path)
+ @new_resource.updated = true
+ else
+ raise "Cannot delete #{@new_resource} at #{@new_resource_path}!"
+ end
+ end
+
+ def action_touch
+ action_create
+ time = Time.now
+ Chef::Log.info("Updating #{@new_resource} with new atime/mtime of #{time}")
+ ::File.utime(time, time, @new_resource.path)
+ @new_resource.updated = true
+ end
+
+ def backup(file=nil)
+ file ||= @new_resource.path
+ if @new_resource.backup && ::File.exist?(file)
+ time = Time.now
+ savetime = time.strftime("%Y%m%d%H%M%S")
+ backup_filename = "#{@new_resource.path}.chef-#{savetime}"
+ Chef::Log.info("Backing up #{@new_resource} to #{backup_filename}")
+ FileUtils.cp(file, backup_filename)
+
+ # Clean up after the number of backups
+ slice_number = @new_resource.backup - 1
+ backup_files = Dir["#{@new_resource.path}.chef-*"].sort { |a,b| b <=> a }
+ if backup_files.length >= @new_resource.backup
+ remainder = backup_files.slice(slice_number..-1)
+ remainder.each do |backup_to_delete|
+ Chef::Log.info("Removing backup of #{@new_resource} at #{backup_to_delete}")
+ FileUtils.rm(backup_to_delete)
+ end
+ end
+
+ end
+ end
+
+ def generate_url(url, type, args=nil)
+ generate_cookbook_url(url, @new_resource.cookbook_name, type, @node, args)
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/link.rb b/chef/lib/chef/provider/link.rb
new file mode 100644
index 0000000000..843a4ad009
--- /dev/null
+++ b/chef/lib/chef/provider/link.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Provider
+ class Link < Chef::Provider
+ def load_current_resource
+ @current_resource = Chef::Resource::Link.new(@new_resource.name)
+ @current_resource.target_file(@new_resource.target_file)
+ @current_resource.link_type(@new_resource.link_type)
+ if @new_resource.link_type == :symbolic
+ if ::File.exists?(@current_resource.target_file) && ::File.symlink?(@current_resource.target_file)
+ @current_resource.source_file(
+ ::File.expand_path(::File.readlink(@current_resource.target_file))
+ )
+ else
+ @current_resource.source_file("")
+ end
+ elsif @new_resource.link_type == :hard
+ if ::File.exists?(@current_resource.target_file) && ::File.exists?(@new_resource.source_file)
+ if ::File.stat(@current_resource.target_file).ino == ::File.stat(@new_resource.source_file).ino
+ @current_resource.source_file(@new_resource.source_file)
+ else
+ @current_resource.source_file("")
+ end
+ else
+ @current_resource.source_file("")
+ end
+ end
+ @current_resource
+ end
+
+ def action_create
+ if @current_resource.source_file != @new_resource.source_file
+ Chef::Log.info("Creating a #{@new_resource.link_type} link from #{@new_resource.source_file} -> #{@new_resource.target_file} for #{@new_resource}")
+ if @new_resource.link_type == :symbolic
+ ::File.symlink(@new_resource.source_file, @new_resource.target_file)
+ elsif @new_resource.link_type == :hard
+ ::File.link(@new_resource.source_file, @new_resource.target_file)
+ end
+ @new_resource.updated = true
+ end
+ end
+
+ def action_delete
+ if ::File.exists?(@new_resource.target_file) && ::File.writable?(@new_resource.target_file)
+ Chef::Log.info("Deleting #{@new_resource} at #{@new_resource.target_file}")
+ ::File.delete(@new_resource.target_file)
+ @new_resource.updated = true
+ else
+ raise "Cannot delete #{@new_resource} at #{@new_resource_path}!"
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/package.rb b/chef/lib/chef/provider/package.rb
new file mode 100644
index 0000000000..a68c2e88f2
--- /dev/null
+++ b/chef/lib/chef/provider/package.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Package < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ def initialize(node, new_resource)
+ super(node, new_resource)
+ @candidate_version = nil
+ end
+
+ def action_install
+ # First, select what version we should be using
+ install_version = @new_resource.version
+ install_version ||= @candidate_version
+
+ unless install_version
+ raise(Chef::Exception::Package, "No version specified, and no candidate version available!")
+ end
+
+ do_package = false
+ # If it's not installed at all, install it
+ if @current_resource.version == nil
+ do_package = true
+ # If we specified a version, and it's not the current version, move to the current version
+ elsif @new_resource.version != nil
+ if @new_resource.version != @current_resource.version
+ do_package = true
+ end
+ end
+
+ if do_package
+ Chef::Log.info("Installing #{@new_resource} version #{install_version} successfully")
+ status = install_package(@new_resource.package_name, install_version)
+ if status
+ @new_resource.updated = true
+ end
+ end
+ end
+
+ def action_upgrade
+ if @current_resource.version != @candidate_version
+ Chef::Log.info("Upgrading #{@new_resource} version from #{@current_resource.version} to #{@candidate_version} successfully")
+ status = install_package(@new_resource.package_name, @candidate_version)
+ if status
+ @new_resource.updated = true
+ end
+ end
+ end
+
+ def action_remove
+ if @current_resource.version != nil
+ Chef::Log.info("Removing #{@new_resource} successfully")
+ remove_package(@new_resource.package_name, @new_resource.version)
+ @new_resource.updated = true
+ end
+ end
+
+ def action_purge
+ if @current_resource.version != nil
+ Chef::Log.info("Purging #{@new_resource} successfully")
+ purge_package(@new_resource.package_name, @new_resource.version)
+ @new_resource.updated = true
+ end
+ end
+
+ def install_package(name, version)
+ raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :install"
+ end
+
+ def upgrade_package(name, version)
+ raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :upgrade"
+ end
+
+ def remove_package(name, version)
+ raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :remove"
+ end
+
+ def purge_package(name, version)
+ raise Chef::Exception::UnsupportedAction, "#{self.to_s} does not support :purge"
+ end
+
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/package/apt.rb b/chef/lib/chef/provider/package/apt.rb
new file mode 100644
index 0000000000..a2e9bd68a2
--- /dev/null
+++ b/chef/lib/chef/provider/package/apt.rb
@@ -0,0 +1,89 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "package")
+require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Package
+ class Apt < Chef::Provider::Package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ status = popen4("apt-cache policy #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdin.close
+ stdout.each do |line|
+ case line
+ when /^\s{2}Installed: (.+)$/
+ installed_version = $1
+ if installed_version == '(none)'
+ @current_resource.version(nil)
+ else
+ @current_resource.version(installed_version)
+ end
+ when /^\s{2}Candidate: (.+)$/
+ @candidate_version = $1
+ end
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exception::Package, "apt-cache failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ run_command(
+ :command => "apt-get -q -y install #{name}=#{version}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ run_command(
+ :command => "apt-get -q -y remove #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ def purge_package(name, version)
+ run_command(
+ :command => "apt-get -q -y purge #{@new_resource.package_name}",
+ :environment => {
+ "DEBIAN_FRONTEND" => "noninteractive"
+ }
+ )
+ end
+
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/package/portage.rb b/chef/lib/chef/provider/package/portage.rb
new file mode 100644
index 0000000000..8981a386bc
--- /dev/null
+++ b/chef/lib/chef/provider/package/portage.rb
@@ -0,0 +1,93 @@
+#
+# Author:: Ezra Zygmuntowicz (<ezra@engineyard.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "package")
+require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Package
+ class Portage < Chef::Provider::Package
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+
+ status = popen4("emerge --color n --nospinner --search #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdin.close
+
+ available, installed = parse_emerge(@new_resource.package_name, stdout.read)
+
+ if installed == "[ Not Installed ]"
+ @current_resource.version(nil)
+ else
+ @current_resource.version(installed)
+ end
+ @candidate_version = available
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exception::Package, "emerge --search failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+
+ def parse_emerge(package, txt)
+ available, installed, pkg = nil
+ txt.each do |line|
+ if line =~ /\*(.*)/
+ pkg = $1.strip.split('/').last
+ end
+ if pkg == package
+ if line =~ /Latest version available: (.*)/
+ available = $1
+ elsif line =~ /Latest version installed: (.*)/
+ installed = $1
+ end
+ end
+ end
+ [available, installed]
+ end
+
+
+ def install_package(name, version)
+ run_command(
+ :command => "emerge -g --color n --nospinner --quiet =#{name}-#{version}"
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ run_command(
+ :command => "emerge --unmerge --color n --nospinner --quiet #{@new_resource.package_name}"
+ )
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/package/rubygems.rb b/chef/lib/chef/provider/package/rubygems.rb
new file mode 100644
index 0000000000..69459ffef4
--- /dev/null
+++ b/chef/lib/chef/provider/package/rubygems.rb
@@ -0,0 +1,116 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "package")
+require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Package
+ class Rubygems < Chef::Provider::Package
+
+ def gem_list_parse(line)
+ installed_versions = Array.new
+ if line.match("^#{@new_resource.package_name} \\((.+?)\\)$")
+ installed_versions = $1.split(/, /)
+ installed_versions
+ else
+ nil
+ end
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Package.new(@new_resource.name)
+ @current_resource.package_name(@new_resource.package_name)
+ @current_resource.version(nil)
+
+ # First, we need to look up whether we have the local gem installed or not
+ status = popen4("gem list --local #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdin.close
+ stdout.each do |line|
+ installed_versions = gem_list_parse(line)
+ next unless installed_versions
+ # If the version we are asking for is installed, make that our current
+ # version. Otherwise, go ahead and use the highest one, which
+ # happens to come first in the array.
+ if installed_versions.detect { |v| v == @new_resource.version }
+ Chef::Log.debug("#{@new_resource.package_name} at version #{@new_resource.version}")
+ @current_resource.version(@new_resource.version)
+ else
+ iv = installed_versions.first
+ Chef::Log.debug("#{@new_resource.package_name} at version #{iv}")
+ @current_resource.version(iv)
+ end
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exception::Package, "gem list --local failed - #{status.inspect}!"
+ end
+
+ status = popen4("gem list --remote #{@new_resource.package_name}") do |pid, stdin, stdout, stderr|
+ stdin.close
+ stdout.each do |line|
+ installed_versions = gem_list_parse(line)
+ next unless installed_versions
+ Chef::Log.debug("I have #{installed_versions.inspect}")
+
+ if installed_versions.length >= 1
+ Chef::Log.debug("Setting candidate version")
+ @candidate_version = installed_versions.first
+ end
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exception::Package, "gem list --remote failed - #{status.inspect}!"
+ end
+
+ @current_resource
+ end
+
+ def install_package(name, version)
+ run_command(
+ :command => "gem install #{name} -q --no-rdoc --no-ri -v #{version}"
+ )
+ end
+
+ def upgrade_package(name, version)
+ install_package(name, version)
+ end
+
+ def remove_package(name, version)
+ if version
+ run_command(
+ :command => "gem uninstall #{name} -q -v #{version}"
+ )
+ else
+ run_command(
+ :command => "gem uninstall #{name} -q -a"
+ )
+ end
+ end
+
+ def purge_package(name, version)
+ remove_package(name, version)
+ end
+
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/remote_directory.rb b/chef/lib/chef/provider/remote_directory.rb
new file mode 100644
index 0000000000..47d62b6308
--- /dev/null
+++ b/chef/lib/chef/provider/remote_directory.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "file")
+require 'uri'
+require 'tempfile'
+require 'net/https'
+
+class Chef
+ class Provider
+ class RemoteDirectory < Chef::Provider::Directory
+
+ def action_create
+ super
+
+ @remote_file_list = Hash.new
+ do_recursive
+ end
+
+ def do_recursive
+ Chef::Log.debug("Doing a recursive directory transfer for #{@new_resource}")
+
+ r = Chef::REST.new(Chef::Config[:remotefile_url])
+
+ files_to_transfer = r.get_rest(generate_url(@new_resource.source, "files", { :recursive => "true" }))
+
+ files_to_transfer.each do |remote_file_source|
+ full_path = ::File.join(@new_resource.path, remote_file_source)
+ full_dir = ::File.dirname(full_path)
+ unless ::File.directory?(full_dir)
+ new_dir = Chef::Resource::Directory.new(full_dir, nil, @node)
+ new_dir.cookbook_name = @new_resource.cookbook_name
+ new_dir.mode(@new_resource.mode)
+ new_dir.group(@new_resource.group)
+ new_dir.owner(@new_resource.owner)
+ new_dir.recursive(true)
+
+ d_provider_class = Chef::Platform.find_provider_for_node(@node, new_dir)
+ d_provider = d_provider_class.new(@node, new_dir)
+ d_provider.load_current_resource
+ d_provider.action_create
+ @new_resource.updated = true if d_provider.new_resource.updated
+ end
+
+ remote_file = Chef::Resource::RemoteFile.new(full_path, nil, @node)
+ remote_file.cookbook_name = @new_resource.cookbook_name
+ remote_file.source(::File.join(@new_resource.source, remote_file_source))
+ remote_file.mode(@new_resource.files_mode) if @new_resource.files_mode
+ remote_file.group(@new_resource.files_group) if @new_resource.files_group
+ remote_file.owner(@new_resource.files_owner) if @new_resource.files_owner
+ remote_file.backup(@new_resource.files_backup) if @new_resource.files_backup
+
+ rf_provider_class = Chef::Platform.find_provider_for_node(@node, remote_file)
+ rf_provider = rf_provider_class.new(@node, remote_file)
+ rf_provider.load_current_resource
+ rf_provider.action_create
+ @new_resource.updated = true if rf_provider.new_resource.updated
+ end
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/remote_file.rb b/chef/lib/chef/provider/remote_file.rb
new file mode 100644
index 0000000000..cc0812346e
--- /dev/null
+++ b/chef/lib/chef/provider/remote_file.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "file")
+require 'uri'
+require 'tempfile'
+require 'net/https'
+
+class Chef
+ class Provider
+ class RemoteFile < Chef::Provider::File
+
+ def action_create
+ Chef::Log.debug("Checking #{@new_resource} for changes")
+ do_remote_file(@new_resource.source, @current_resource.path)
+ end
+
+ def do_remote_file(source, path)
+ r = Chef::REST.new(Chef::Config[:remotefile_url])
+
+ current_checksum = nil
+ current_checksum = self.checksum(path) if ::File.exists?(path)
+
+ url = generate_url(
+ source,
+ "files",
+ {
+ :checksum => current_checksum
+ }
+ )
+
+ raw_file = nil
+ begin
+ raw_file = r.get_rest(url, true)
+ rescue Net::HTTPRetriableError => e
+ if e.response.kind_of?(Net::HTTPNotModified)
+ Chef::Log.debug("File #{path} is unchanged")
+ return false
+ else
+ raise e
+ end
+ end
+
+ raw_file_checksum = self.checksum(raw_file.path)
+
+ if ::File.exists?(path)
+ Chef::Log.debug("#{path} changed from #{current_checksum} to #{raw_file_checksum}")
+ Chef::Log.info("Updating file for #{@new_resource} at #{path}")
+ else
+ Chef::Log.info("Creating file for #{@new_resource} at #{path}")
+ end
+
+ backup(path)
+ FileUtils.cp(raw_file.path, path)
+ @new_resource.updated = true
+
+ set_owner if @new_resource.owner != nil
+ set_group if @new_resource.group != nil
+ set_mode if @new_resource.mode != nil
+ return true
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/script.rb b/chef/lib/chef/provider/script.rb
new file mode 100644
index 0000000000..4669361142
--- /dev/null
+++ b/chef/lib/chef/provider/script.rb
@@ -0,0 +1,35 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'tempfile'
+
+class Chef
+ class Provider
+ class Script < Chef::Provider::Execute
+
+ def action_run
+ tf = Tempfile.new("chef-script")
+ tf.puts(@new_resource.code)
+ tf.close
+ @new_resource.command("#{@new_resource.interpreter} #{tf.path}")
+ super
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/service.rb b/chef/lib/chef/provider/service.rb
new file mode 100644
index 0000000000..5ac53478e3
--- /dev/null
+++ b/chef/lib/chef/provider/service.rb
@@ -0,0 +1,86 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Service < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ def initialize(node, new_resource)
+ super(node, new_resource)
+ @enabled = nil
+ end
+
+ def action_enable
+ if @current_resource.enabled == false
+ Chef::Log.debug("#{@new_resource}: attempting to enable")
+ status = enable_service(@new_resource.service_name)
+ if status
+ @new_resource.enabled == true
+ Chef::Log.info("#{@new_resource}: enabled succesfully")
+ end
+ else
+ Chef::Log.debug("#{@new_resource}: not enabling, already enabled")
+ end
+ end
+
+ def action_disable
+ if @current_resource.enabled == true
+ Chef::Log.debug("#{@new_resource}: attempting to disable")
+ status = disable_service(@new_resource.service_name)
+ if status
+ @new_resource.enabled == false
+ Chef::Log.info("#{@new_resource}: disabled succesfully")
+ end
+ else
+ Chef::Log.debug("#{@new_resource}: not disabling, already disabled")
+ end
+ end
+
+ def action_start
+ if @current_resource.running == false
+ Chef::Log.debug("#{@new_resource}: attempting to start")
+ status = start_service(@new_resource.service_name)
+ if status
+ @new_resource.running == true
+ Chef::Log.info("Started service #{@new_resource} succesfully")
+ end
+ else
+ Chef::Log.debug("#{@new_resource}: not starting, already running")
+ end
+ end
+
+ def action_stop
+ if @current_resource.running == true
+ Chef::Log.debug("#{@new_resource}: attempting to stop")
+ status = stop_service(@new_resource.service_name)
+ if status
+ @new_resource.running == false
+ Chef::Log.info("#{@new_resource}: stopped succesfully")
+ end
+ else
+ Chef::Log.debug("#{@new_resource}: not stopping, already stopped")
+ end
+ end
+
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/service/debian.rb b/chef/lib/chef/provider/service/debian.rb
new file mode 100644
index 0000000000..21f166ba6a
--- /dev/null
+++ b/chef/lib/chef/provider/service/debian.rb
@@ -0,0 +1,53 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "service")
+require File.join(File.dirname(__FILE__), "init")
+require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Service
+ class Debian < Chef::Provider::Service::Init
+ def load_current_resource
+ super
+
+ status = popen4("update-rc.d -n -f #{@current_resource.service_name} remove") do |pid, stdin, stdout, stderr|
+ stdin.close
+ stdout.gets(nil) =~ /etc\/rc[\dS].d\/S|not installed/i ? @current_resource.enabled(true) : @current_resource.enabled(false)
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exception::Service, "update-rc.d -n -f #{@current_resource.service_name} failed - #{status.inspect}"
+ end
+
+ @current_resource
+ end
+
+ def enable_service(name)
+ run_command(:command => "update-rc.d #{name} defaults")
+ end
+
+ def disable_service(name)
+ run_command(:command => "update-rc.d -f #{name} remove")
+ end
+
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/service/init.rb b/chef/lib/chef/provider/service/init.rb
new file mode 100644
index 0000000000..0ccfc232d8
--- /dev/null
+++ b/chef/lib/chef/provider/service/init.rb
@@ -0,0 +1,95 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "service")
+require File.join(File.dirname(__FILE__), "..", "..", "mixin", "command")
+
+class Chef
+ class Provider
+ class Service
+ class Init < Chef::Provider::Service
+
+ def load_current_resource
+ @current_resource = Chef::Resource::Service.new(@new_resource.name)
+ @current_resource.service_name(@new_resource.service_name)
+ process_running = false
+ if @new_resource.supports[:status]
+ run_command(:command => "/etc/init.d/#{@current_resource.service_name} status") == 0 ? process_running = true : process_running = false
+ elsif @new_resource.status_command
+ run_command(:command => @new_resource.status_command) == 0 ? process_running = true : process_running = false
+ else
+ Chef::Log.debug("#{@new_resource} does not support status and you have not specified a status command, falling back to process table inspection")
+ if @new_resource.pattern == @new_resource.service_name
+ Chef::Log.debug("#{@new_resource} defaulting pattern to #{Regex.new(@new_resource.pattern)}")
+ elsif @node[:ps] == ""
+ raise Chef::Exception::Service, "#{@new_resource}: Facter could not determine how to call `ps` on your system (#{Facter["ps"].value})"
+ end
+
+ process_pid = nil
+ status = popen4(@node[:ps]) do |pid, stdin, stdout, stderr|
+ stdin.close
+ r = Regexp.new(@new_resource.pattern)
+ Chef::Log.debug("#{@new_resource}: attempting to match #{@new_resource.pattern} (#{r}) against process table")
+ stdout.each_line do |line|
+ if r.match(line)
+ process_pid = line.sub(/^\s+/, '').split(/\s+/)[1]
+ end
+ end
+ end
+ unless status.exitstatus == 0
+ raise Chef::Exception::Service, "Command #{@node[:ps]} failed"
+ else
+ process_pid ? process_running = true : process_running = false
+ Chef::Log.debug("#{@new_resource}: #{@node[:ps]} exited succesfully, process_running: #{process_running}")
+ end
+ end
+ @current_resource.running process_running
+ @current_resource
+ end
+
+ def start_service(name)
+ if @new_resource.start_command
+ run_command(:command => @new_resource.start_command)
+ else
+ run_command(:command => "/etc/init.d/#{name} start")
+ end
+ end
+
+ def stop_service(name)
+ if @new_resource.stop_command
+ run_command(:command => @new_resource.stop_command)
+ else
+ run_command(:command => "/etc/init.d/#{name} stop")
+ end
+ end
+
+ def restart_service(name)
+ if @new_resource.supports[:restart]
+ run_command(:command => "/etc/init.d/#{name} restart")
+ elsif @new_resource.restart_command
+ run_command(:command => @new_resource.restart_command)
+ else
+ stop_service
+ start_service
+ end
+ end
+
+ end
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/sysctl.rb b/chef/lib/chef/provider/sysctl.rb
new file mode 100644
index 0000000000..09b5942f5e
--- /dev/null
+++ b/chef/lib/chef/provider/sysctl.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "file")
+require "fileutils"
+
+class Chef
+ class Provider
+ class Sysctl < Chef::Provider
+ def load_current_resource
+ @current_resource = Chef::Resource::Sysctl.new(@new_resource.name)
+ @current_resource.value(`/sbin/sysctl #{@new_resource.name}`.chomp)
+ @current_resource
+ end
+
+ def action_set
+ if @current_resource.value != @new_resource.value
+ system("/sbin/sysctl #{@new_resource.name}=#{@new_resource.value}")
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/template.rb b/chef/lib/chef/provider/template.rb
new file mode 100644
index 0000000000..550a1388bf
--- /dev/null
+++ b/chef/lib/chef/provider/template.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "file")
+require File.join(File.dirname(__FILE__), "..", "mixin", "template")
+require 'uri'
+require 'tempfile'
+
+class Chef
+ class Provider
+ class Template < Chef::Provider::File
+
+ include Chef::Mixin::Template
+
+ def action_create
+ r = Chef::REST.new(Chef::Config[:template_url])
+
+ template_url = generate_url(@new_resource.source, "templates")
+ raw_template_file = r.get_rest(template_url, true)
+
+ context = @new_resource.variables
+ context[:node] = @node
+ template_file = render_template(::File.read(raw_template_file.path), context)
+
+ update = false
+
+ if ::File.exists?(@new_resource.path)
+ @new_resource.checksum(self.checksum(template_file.path))
+ if @new_resource.checksum != @current_resource.checksum
+ Chef::Log.debug("#{@new_resource} changed from #{@current_resource.checksum} to #{@new_resource.checksum}")
+ Chef::Log.info("Updating #{@new_resource} at #{@new_resource.path}")
+ update = true
+ end
+ else
+ Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}")
+ update = true
+ end
+
+ if update
+ backup
+ FileUtils.cp(template_file.path, @new_resource.path)
+ @new_resource.updated = true
+ else
+ Chef::Log.debug("#{@new_resource} is unchanged")
+ end
+
+ set_owner if @new_resource.owner != nil
+ set_group if @new_resource.group != nil
+ set_mode if @new_resource.mode != nil
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/provider/user.rb b/chef/lib/chef/provider/user.rb
new file mode 100644
index 0000000000..a3de68d93c
--- /dev/null
+++ b/chef/lib/chef/provider/user.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "provider")
+require File.join(File.dirname(__FILE__), "..", "mixin", "command")
+require 'etc'
+
+class Chef
+ class Provider
+ class User < Chef::Provider
+
+ include Chef::Mixin::Command
+
+ def initialize(node, new_resource)
+ super(node, new_resource)
+ @user_exists = true
+ @locked = nil
+ end
+
+ def load_current_resource
+ @current_resource = Chef::Resource::User.new(@new_resource.name)
+ @current_resource.username(@new_resource.username)
+
+ user_info = nil
+ begin
+ user_info = Etc.getpwnam(@new_resource.username)
+ rescue ArgumentError => e
+ @user_exists = false
+ Chef::Log.debug("User #{@new_resource.username} does not exist")
+ end
+
+ if user_info
+ @current_resource.uid(user_info.uid)
+ @current_resource.gid(user_info.gid)
+ @current_resource.comment(user_info.gecos)
+ @current_resource.home(user_info.dir)
+ @current_resource.shell(user_info.shell)
+
+ if @new_resource.password
+ begin
+ require 'shadow'
+ rescue Exception => e
+ Chef::Log.error("You must have ruby-shadow installed for password support!")
+ raise Chef::Exception::MissingLibrary, "You must have ruby-shadow installed for password support!"
+ end
+ shadow_info = Shadow::Passwd.getspnam(@new_resource.username)
+ @current_resource.password(shadow_info.sp_pwdp)
+ end
+ end
+
+ @current_resource
+ end
+
+ def compare_user
+ change_required = false
+ change_required = true if @new_resource.uid != @current_resource.uid
+ change_required = true if @new_resource.gid != @current_resource.gid
+ change_required = true if @new_resource.comment != @current_resource.comment
+ change_required = true if @new_resource.home != @current_resource.home
+ change_required = true if @new_resource.shell != @current_resource.shell
+ change_required = true if @new_resource.password != @current_resource.password
+ change_required
+ end
+
+ def action_create
+ case @user_exists
+ when false
+ create_user
+ Chef::Log.info("Created #{@new_resource}")
+ @new_resource.updated = true
+ else
+ if compare_user
+ manage_user
+ Chef::Log.info("Altered #{@new_resource}")
+ @new_resource.updated = true
+ end
+ end
+ end
+
+ def action_remove
+ if @user_exists
+ remove_user
+ @new_resource.updated = true
+ Chef::Log.info("Removed #{@new_resource}")
+ end
+ end
+
+ def action_manage
+ if @user_exists && compare_user
+ manage_user
+ @new_resource.updated = true
+ Chef::Log.info("Managed #{@new_resource}")
+ end
+ end
+
+ def action_modify
+ if @user_exists && compare_user
+ manage_user
+ @new_resource.updated = true
+ Chef::Log.info("Modified #{@new_resource}")
+ else
+ raise Chef::Exception::User, "Cannot modify #{@new_resource} - user does not exist!"
+ end
+ end
+
+ def check_lock
+ status = popen4("passwd -S #{@new_resource.username}") do |pid, stdin, stdout, stderr|
+ stdin.close
+ status_line = stdout.gets.split(' ')
+ case status_line[1]
+ when /^P/
+ @locked = false
+ when /^N/
+ @locked = false
+ when /^L/
+ @locked = true
+ end
+ end
+
+ unless status.exitstatus == 0
+ raise Chef::Exception::User, "Cannot determine if #{@new_resource} is locked!"
+ end
+
+ @locked
+ end
+
+ def action_lock
+ if @user_exists
+ if check_lock() == false
+ lock_user
+ @new_resource.updated = true
+ Chef::Log.info("Locked #{@new_resource}")
+ else
+ Chef::Log.debug("No need to lock #{@new_resource}")
+ end
+ else
+ raise Chef::Exception::User, "Cannot lock #{@new_resource} - user does not exist!"
+ end
+ end
+
+ def action_unlock
+ if @user_exists
+ if check_lock() == true
+ unlock_user
+ @new_resource.updated = true
+ Chef::Log.info("Unlocked #{@new_resource}")
+ else
+ Chef::Log.debug("No need to unlock #{@new_resource}")
+ end
+ else
+ raise Chef::Exception::User, "Cannot unlock #{@new_resource} - user does not exist!"
+ end
+ end
+
+ end
+ end
+end
diff --git a/chef/lib/chef/provider/user/useradd.rb b/chef/lib/chef/provider/user/useradd.rb
new file mode 100644
index 0000000000..96736a27a8
--- /dev/null
+++ b/chef/lib/chef/provider/user/useradd.rb
@@ -0,0 +1,88 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", "user")
+
+class Chef
+ class Provider
+ class User
+ class Useradd < Chef::Provider::User
+ def create_user
+ command = "useradd"
+ command << set_options
+ run_command(:command => command)
+ end
+
+ def manage_user
+ command = "usermod"
+ command << set_options
+ run_command(:command => command)
+ end
+
+ def remove_user
+ command = "userdel"
+ command << " -r" if @new_resource.supports[:manage_home]
+ command << " #{@new_resource.username}"
+ run_command(:command => command)
+ end
+
+ def lock_user
+ run_command(:command => "usermod -L #{@new_resource.username}")
+ end
+
+ def unlock_user
+ run_command(:command => "usermod -U #{@new_resource.username}")
+ end
+
+ def set_options
+ opts = ''
+
+ field_list = {
+ 'comment' => "-c",
+ 'home' => "-d",
+ 'gid' => "-g",
+ 'uid' => "-u",
+ 'shell' => "-s",
+ 'password' => "-p"
+ }
+ field_list.each do |field, option|
+ field_symbol = field.to_sym
+ if @current_resource.send(field_symbol) != @new_resource.send(field_symbol)
+ if @new_resource.send(field_symbol)
+ Chef::Log.debug("Setting #{@new_resource} #{field} to #{@new_resource.send(field_symbol)}")
+ opts << " #{option} '#{@new_resource.send(field_symbol)}'"
+ end
+ end
+ end
+ if @new_resource.supports[:manage_home]
+ Chef::Log.debug("Managing the home directory for #{@new_resource}")
+ case @node[:operatingsystem]
+ when "Fedora","RedHat","CentOS"
+ opts << " -M"
+ else
+ opts << " -m"
+ end
+ end
+ opts << " #{@new_resource.username}"
+ opts
+ end
+
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/queue.rb b/chef/lib/chef/queue.rb
new file mode 100644
index 0000000000..86eb7df257
--- /dev/null
+++ b/chef/lib/chef/queue.rb
@@ -0,0 +1,107 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+
+class Chef
+ class Queue
+ require 'stomp'
+
+ @client = nil
+
+ class << self
+ include Chef::Mixin::ParamsValidate
+
+ def connect
+ @client = Stomp::Connection.open(
+ Chef::Config.has_key?(:queue_user) ? Chef::Config[:queue_user] : "",
+ Chef::Config.has_key?(:queue_password) ? Chef::Config[:queue_password] : "",
+ Chef::Config.has_key?(:queue_host) ? Chef::Config[:queue_host] : "localhost",
+ Chef::Config.has_key?(:queue_port) ? Chef::Config[:queue_port] : 61613,
+ false
+ )
+ end
+
+ def make_url(type, name)
+ validate(
+ {
+ :queue_type => type.to_sym,
+ :queue_name => name.to_sym,
+ },
+ {
+ :queue_type => {
+ :equal_to => [ :topic, :queue ],
+ },
+ :queue_name => {
+ :kind_of => [ String, Symbol ],
+ }
+ }
+ )
+ queue_url = "/#{type}/chef/#{name}"
+ end
+
+ def subscribe(type, name)
+ queue_url = make_url(type, name)
+ Chef::Log.debug("Subscribing to #{queue_url}")
+ connect if @client == nil
+ @client.subscribe(queue_url)
+ end
+
+ def send_msg(type, name, msg)
+ validate(
+ {
+ :message => msg,
+ },
+ {
+ :message => {
+ :respond_to => :to_json
+ }
+ }
+ )
+ queue_url = make_url(type, name)
+ json = msg.to_json
+ connect if @client == nil
+ Chef::Log.debug("Sending to #{queue_url}: #{json}")
+ @client.send(queue_url, json)
+ end
+
+ def receive_msg
+ connect if @client == nil
+ raw_msg = @client.receive()
+ Chef::Log.debug("Received Message from #{raw_msg.headers["destination"]} containing: #{raw_msg.body}")
+ msg = JSON.parse(raw_msg.body)
+ return msg, raw_msg.headers
+ end
+
+ def poll_msg
+ connect if @client == nil
+ raw_msg = @client.poll()
+ if raw_msg
+ msg = JSON.parse(raw_msg.body)
+ else
+ nil
+ end
+ end
+
+ def disconnect
+ raise ArgumentError, "You must call connect before you can disconnect!" unless @client
+ @client.disconnect
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb
new file mode 100644
index 0000000000..e2e20640f7
--- /dev/null
+++ b/chef/lib/chef/recipe.rb
@@ -0,0 +1,127 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "from_file")
+
+class Chef
+ class Recipe
+
+ include Chef::Mixin::FromFile
+
+ attr_accessor :cookbook_name, :recipe_name, :recipe, :node, :collection,
+ :definitions, :params, :cookbook_loader
+
+ def initialize(cookbook_name, recipe_name, node, collection=nil, definitions=nil, cookbook_loader=nil)
+ @cookbook_name = cookbook_name
+ @recipe_name = recipe_name
+ @node = node
+
+ if collection
+ @collection = collection
+ else
+ @collection = Chef::ResourceCollection.new()
+ end
+
+ if definitions
+ @definitions = definitions
+ else
+ @definitions = Hash.new
+ end
+
+ if cookbook_loader
+ @cookbook_loader = cookbook_loader
+ else
+ @cookbook_loader = Chef::CookbookLoader.new()
+ end
+
+ @params = Hash.new
+ end
+
+ def require_recipe(*args)
+ args.flatten.each do |recipe|
+ rmatch = recipe.match(/(.+?)::(.+)/)
+ if rmatch
+ cookbook = @cookbook_loader[rmatch[1]]
+ cookbook.load_recipe(rmatch[2], @node, @collection, @definitions, @cookbook_loader)
+ else
+ cookbook = @cookbook_loader[recipe]
+ cookbook.load_recipe("default", @node, @collection, @definitions, @cookbook_loader)
+ end
+ end
+ end
+
+ def resources(*args)
+ @collection.resources(*args)
+ end
+
+ def search(type, query, &block)
+ Chef::Log.debug("Searching #{type} index with #{query}")
+ r = Chef::REST.new(Chef::Config[:search_url])
+ results = r.get_rest("search/#{type}?q=#{query}")
+ Chef::Log.debug("Searching #{type} index with #{query} returned #{results.length} entries")
+ results.each do |sr|
+ block.call(sr)
+ end
+ end
+
+ def method_missing(method_symbol, *args, &block)
+ resource = nil
+ # If we have a definition that matches, we want to use that instead. This should
+ # let you do some really crazy over-riding of "native" types, if you really want
+ # to.
+ if @definitions.has_key?(method_symbol)
+ new_def = @definitions[method_symbol].dup
+ new_def.instance_eval(&block) if block
+ new_recipe = Chef::Recipe.new(@cookbook_name, @recipe_name, @node, @collection, @definitions, @cookbook_loader)
+ new_recipe.params = new_def.params
+ new_recipe.params[:name] = args[0]
+ new_recipe.instance_eval(&new_def.recipe)
+ else
+ method_name = method_symbol.to_s
+ # Otherwise, we're rocking the regular resource call route.
+ rname = nil
+ mn = method_name.match(/^(.+)_(.+)$/)
+ if mn
+ rname = "Chef::Resource::#{mn[1].capitalize}#{mn[2].capitalize}"
+ else
+ short_match = method_name.match(/^(.+)$/)
+ if short_match
+ rname = "Chef::Resource::#{short_match[1].capitalize}"
+ end
+ end
+ begin
+ args << @collection
+ args << @node
+ resource = eval(rname).new(*args)
+ resource.cookbook_name = @cookbook_name
+ resource.recipe_name = @recipe_name
+ resource.params = @params
+ resource.instance_eval(&block) if block
+ rescue Exception => e
+ if e.kind_of?(NameError) && e.to_s =~ /Chef::Resource/
+ raise NameError, "Cannot find #{rname} for #{method_name}\nOriginal: #{e.to_s}"
+ else
+ raise e
+ end
+ end
+ @collection << resource
+ resource
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb
new file mode 100644
index 0000000000..75349a7927
--- /dev/null
+++ b/chef/lib/chef/resource.rb
@@ -0,0 +1,172 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require File.join(File.dirname(__FILE__), "mixin", "check_helper")
+
+class Chef
+ class Resource
+
+ include Chef::Mixin::CheckHelper
+ include Chef::Mixin::ParamsValidate
+
+ attr_accessor :actions, :params, :provider, :updated, :allowed_actions, :collection, :cookbook_name, :recipe_name, :supports
+ attr_reader :resource_name, :source_line, :node
+
+ def initialize(name, collection=nil, node=nil)
+ @name = name
+ if collection
+ @collection = collection
+ else
+ @collection = Chef::ResourceCollection.new()
+ end
+ @node = node ? node : Chef::Node.new
+ @noop = nil
+ @before = nil
+ @actions = Hash.new
+ @params = Hash.new
+ @provider = nil
+ @allowed_actions = [ :nothing ]
+ @action = :nothing
+ @updated = false
+ @supports = {}
+ @source_line = caller(4).shift.gsub!(/^(.+):(.+):.+$/, '\1 line \2')
+ @source_line = ::File.expand_path(@source_line) if @source_line
+ end
+
+ def provider(arg=nil)
+ set_or_return(
+ :provider,
+ arg,
+ :kind_of => [ Class ]
+ )
+ end
+
+ def action(arg=nil)
+ if arg
+ action_list = arg.kind_of?(Array) ? arg : [ arg ]
+ action_list.each do |action|
+ validate(
+ {
+ :action => action,
+ },
+ {
+ :object => { :equal_to => @allowed_actions },
+ }
+ )
+ end
+ @action = action_list
+ else
+ @action
+ end
+ end
+
+ def name(name=nil)
+ set_if_args(@name, name) do
+ raise ArgumentError, "name must be a string!" unless name.kind_of?(String)
+ @name = name
+ end
+ end
+
+ def noop(tf=nil)
+ set_if_args(@noop, tf) do
+ raise ArgumentError, "noop must be true or false!" unless tf == true || tf == false
+ @noop = tf
+ end
+ end
+
+ def notifies(action, resources, timing=:delayed)
+ timing = check_timing(timing)
+ rarray = resources.kind_of?(Array) ? resources : [ resources ]
+ rarray.each do |resource|
+ action_sym = action.to_sym
+ if @actions.has_key?(action_sym)
+ @actions[action_sym][timing] << resource
+ else
+ @actions[action_sym] = Hash.new
+ @actions[action_sym][:delayed] = Array.new
+ @actions[action_sym][:immediate] = Array.new
+ @actions[action_sym][timing] << resource
+ end
+ end
+ true
+ end
+
+ def resources(*args)
+ @collection.resources(*args)
+ end
+
+ def subscribes(action, resources, timing=:delayed)
+ timing = check_timing(timing)
+ rarray = resources.kind_of?(Array) ? resources : [ resources ]
+ rarray.each do |resource|
+ action_sym = action.to_sym
+ if resource.actions.has_key?(action_sym)
+ resource.actions[action_sym][timing] << self
+ else
+ resource.actions[action_sym] = Hash.new
+ resource.actions[action_sym][:delayed] = Array.new
+ resource.actions[action_sym][:immediate] = Array.new
+ resource.actions[action_sym][timing] << self
+ end
+ end
+ true
+ end
+
+ def is(*args)
+ return *args
+ end
+
+ def to_s
+ "#{@resource_name}[#{@name}]"
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ instance_vars = Hash.new
+ self.instance_variables.each do |iv|
+ instance_vars[iv] = self.instance_variable_get(iv) unless iv == "@collection"
+ end
+ results = {
+ 'json_class' => self.class.name,
+ 'instance_vars' => instance_vars
+ }
+ results.to_json(*a)
+ end
+
+ def self.json_create(o)
+ resource = self.new(o["instance_vars"]["@name"])
+ o["instance_vars"].each do |k,v|
+ resource.instance_variable_set(k.to_sym, v)
+ end
+ resource
+ end
+
+ private
+
+ def check_timing(timing)
+ unless timing == :delayed || timing == :immediate || timing == :immediately
+ raise ArgumentError, "Timing must be :delayed or :immediate(ly), you said #{timing}"
+ end
+ if timing == :immediately
+ timing = :immediate
+ end
+ timing
+ end
+ end
+end
diff --git a/chef/lib/chef/resource/apt_package.rb b/chef/lib/chef/resource/apt_package.rb
new file mode 100644
index 0000000000..58e8b14edc
--- /dev/null
+++ b/chef/lib/chef/resource/apt_package.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "package")
+
+class Chef
+ class Resource
+ class AptPackage < Chef::Resource::Package
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :apt_package
+ @provider = Chef::Provider::Package::Apt
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/bash.rb b/chef/lib/chef/resource/bash.rb
new file mode 100644
index 0000000000..7af5f9756a
--- /dev/null
+++ b/chef/lib/chef/resource/bash.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "script")
+
+class Chef
+ class Resource
+ class Bash < Chef::Resource::Script
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :bash
+ @interpreter = "bash"
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/csh.rb b/chef/lib/chef/resource/csh.rb
new file mode 100644
index 0000000000..29de9777b9
--- /dev/null
+++ b/chef/lib/chef/resource/csh.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "script")
+
+class Chef
+ class Resource
+ class Csh < Chef::Resource::Script
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :csh
+ @interpreter = "csh"
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/directory.rb b/chef/lib/chef/resource/directory.rb
new file mode 100644
index 0000000000..7e8944f5fc
--- /dev/null
+++ b/chef/lib/chef/resource/directory.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Directory < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :directory
+ @path = name
+ @action = :create
+ @recursive = false
+ @allowed_actions.push(:create, :delete)
+ end
+
+ def recursive(arg=nil)
+ set_or_return(
+ :recursive,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ def group(arg=nil)
+ set_or_return(
+ :group,
+ arg,
+ :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
+ )
+ end
+
+ def mode(arg=nil)
+ set_or_return(
+ :mode,
+ arg,
+ :regex => /^\d{3,4}$/
+ )
+ end
+
+ def owner(arg=nil)
+ set_or_return(
+ :owner,
+ arg,
+ :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
+ )
+ end
+
+ def path(arg=nil)
+ set_or_return(
+ :path,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/execute.rb b/chef/lib/chef/resource/execute.rb
new file mode 100644
index 0000000000..232cfe4d64
--- /dev/null
+++ b/chef/lib/chef/resource/execute.rb
@@ -0,0 +1,133 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Execute < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :execute
+ @command = name
+ @backup = 5
+ @action = "run"
+ @creates = nil
+ @cwd = nil
+ @environment = nil
+ @group = nil
+ @onlyif = nil
+ @path = nil
+ @notify_only = false
+ @returns = 0
+ @timeout = nil
+ @not_if = nil
+ @user = nil
+ @allowed_actions.push(:run)
+ end
+
+ def command(arg=nil)
+ set_or_return(
+ :command,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def creates(arg=nil)
+ set_or_return(
+ :creates,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def cwd(arg=nil)
+ set_or_return(
+ :cwd,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def environment(arg=nil)
+ set_or_return(
+ :environment,
+ arg,
+ :kind_of => [ Hash ]
+ )
+ end
+
+ def group(arg=nil)
+ set_or_return(
+ :group,
+ arg,
+ :kind_of => [ String, Integer ]
+ )
+ end
+
+ def onlyif(arg=nil)
+ set_or_return(
+ :onlyif,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def path(arg=nil)
+ set_or_return(
+ :path,
+ arg,
+ :kind_of => [ Array ]
+ )
+ end
+
+ def returns(arg=nil)
+ set_or_return(
+ :returns,
+ arg,
+ :kind_of => [ Integer ]
+ )
+ end
+
+ def timeout(arg=nil)
+ set_or_return(
+ :timeout,
+ arg,
+ :kind_of => [ Integer ]
+ )
+ end
+
+ def not_if(arg=nil)
+ set_or_return(
+ :not_if,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def user(arg=nil)
+ set_or_return(
+ :user,
+ arg,
+ :kind_of => [ String, Integer ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/file.rb b/chef/lib/chef/resource/file.rb
new file mode 100644
index 0000000000..099bd76591
--- /dev/null
+++ b/chef/lib/chef/resource/file.rb
@@ -0,0 +1,82 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class File < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :file
+ @path = name
+ @backup = 5
+ @action = "create"
+ @allowed_actions.push(:create, :delete, :touch)
+ end
+
+ def backup(arg=nil)
+ set_or_return(
+ :backup,
+ arg,
+ :kind_of => [ Integer, FalseClass ]
+ )
+ end
+
+ def checksum(arg=nil)
+ set_or_return(
+ :checksum,
+ arg,
+ :regex => /^[a-zA-Z0-9]{32}$/
+ )
+ end
+
+ def group(arg=nil)
+ set_or_return(
+ :group,
+ arg,
+ :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
+ )
+ end
+
+ def mode(arg=nil)
+ set_or_return(
+ :mode,
+ arg,
+ :regex => /^\d{3,4}$/
+ )
+ end
+
+ def owner(arg=nil)
+ set_or_return(
+ :owner,
+ arg,
+ :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
+ )
+ end
+
+ def path(arg=nil)
+ set_or_return(
+ :path,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/gem_package.rb b/chef/lib/chef/resource/gem_package.rb
new file mode 100644
index 0000000000..1ec9463337
--- /dev/null
+++ b/chef/lib/chef/resource/gem_package.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "package")
+
+class Chef
+ class Resource
+ class GemPackage < Chef::Resource::Package
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :gem_package
+ @provider = Chef::Provider::Package::Rubygems
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/link.rb b/chef/lib/chef/resource/link.rb
new file mode 100644
index 0000000000..46b5793b0f
--- /dev/null
+++ b/chef/lib/chef/resource/link.rb
@@ -0,0 +1,60 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Link < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :link
+ @source_file = name
+ @action = :create
+ @link_type = :symbolic
+ @target_file = nil
+ @allowed_actions.push(:create, :delete)
+ end
+
+ def source_file(arg=nil)
+ set_or_return(
+ :source_file,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ def target_file(arg=nil)
+ set_or_return(
+ :target_file,
+ arg,
+ :kind_of => String
+ )
+ end
+
+ def link_type(arg=nil)
+ real_arg = arg.kind_of?(String) ? arg.to_sym : arg
+ set_or_return(
+ :link_type,
+ real_arg,
+ :equal_to => [ :symbolic, :hard ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/package.rb b/chef/lib/chef/resource/package.rb
new file mode 100644
index 0000000000..f5f1c8418e
--- /dev/null
+++ b/chef/lib/chef/resource/package.rb
@@ -0,0 +1,69 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Package < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :package
+ @package_name = name
+ @version = nil
+ @candidate_version = nil
+ @response_file = nil
+ @source = nil
+ @action = "install"
+ @allowed_actions.push(:install, :upgrade, :remove, :purge)
+ end
+
+ def package_name(arg=nil)
+ set_or_return(
+ :package_name,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def version(arg=nil)
+ set_or_return(
+ :version,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def response_file(arg=nil)
+ set_or_return(
+ :response_file,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def source(arg=nil)
+ set_or_return(
+ :source,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/perl.rb b/chef/lib/chef/resource/perl.rb
new file mode 100644
index 0000000000..a9b891ed5c
--- /dev/null
+++ b/chef/lib/chef/resource/perl.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "script")
+
+class Chef
+ class Resource
+ class Perl < Chef::Resource::Script
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :perl
+ @interpreter = "perl"
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/portage_package.rb b/chef/lib/chef/resource/portage_package.rb
new file mode 100644
index 0000000000..0e300da7a3
--- /dev/null
+++ b/chef/lib/chef/resource/portage_package.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "package")
+
+class Chef
+ class Resource
+ class PortagePackage < Chef::Resource::Package
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :portage_package
+ @provider = Chef::Provider::Package::Apt
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/python.rb b/chef/lib/chef/resource/python.rb
new file mode 100644
index 0000000000..ac2b27c00f
--- /dev/null
+++ b/chef/lib/chef/resource/python.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "script")
+
+class Chef
+ class Resource
+ class Python < Chef::Resource::Script
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :python
+ @interpreter = "python"
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/remote_directory.rb b/chef/lib/chef/resource/remote_directory.rb
new file mode 100644
index 0000000000..a23b39d42c
--- /dev/null
+++ b/chef/lib/chef/resource/remote_directory.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class RemoteDirectory < Chef::Resource::Directory
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :remote_directory
+ @path = name
+ @delete = false
+ @action = :create
+ @recursive = true
+ @files_backup = 5
+ @files_owner = nil
+ @files_group = nil
+ @files_mode = 0644
+ @allowed_actions.push(:create, :delete)
+ end
+
+ def source(args=nil)
+ set_or_return(
+ :source,
+ args,
+ :kind_of => String
+ )
+ end
+
+ def files_backup(arg=nil)
+ set_or_return(
+ :files_backup,
+ arg,
+ :kind_of => [ Integer, FalseClass ]
+ )
+ end
+
+ def files_group(arg=nil)
+ set_or_return(
+ :files_group,
+ arg,
+ :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
+ )
+ end
+
+ def files_mode(arg=nil)
+ set_or_return(
+ :files_mode,
+ arg,
+ :regex => /^\d{3,4}$/
+ )
+ end
+
+ def files_owner(arg=nil)
+ set_or_return(
+ :files_owner,
+ arg,
+ :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/remote_file.rb b/chef/lib/chef/resource/remote_file.rb
new file mode 100644
index 0000000000..dace234398
--- /dev/null
+++ b/chef/lib/chef/resource/remote_file.rb
@@ -0,0 +1,41 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class RemoteFile < Chef::Resource::File
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :remote_file
+ @action = "create"
+ @source = nil
+ @variables = Hash.new
+ end
+
+ def source(args=nil)
+ set_or_return(
+ :source,
+ args,
+ :kind_of => String
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/ruby.rb b/chef/lib/chef/resource/ruby.rb
new file mode 100644
index 0000000000..591f1a658b
--- /dev/null
+++ b/chef/lib/chef/resource/ruby.rb
@@ -0,0 +1,33 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "script")
+
+class Chef
+ class Resource
+ class Ruby < Chef::Resource::Script
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :ruby
+ @interpreter = "ruby"
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/script.rb b/chef/lib/chef/resource/script.rb
new file mode 100644
index 0000000000..d59882ab9c
--- /dev/null
+++ b/chef/lib/chef/resource/script.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "execute")
+
+class Chef
+ class Resource
+ class Script < Chef::Resource::Execute
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :script
+ @command = name
+ @code = nil
+ @interpreter = nil
+ end
+
+ def code(arg=nil)
+ set_or_return(
+ :code,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def interpreter(arg=nil)
+ set_or_return(
+ :interpreter,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/service.rb b/chef/lib/chef/resource/service.rb
new file mode 100644
index 0000000000..1c9a784745
--- /dev/null
+++ b/chef/lib/chef/resource/service.rb
@@ -0,0 +1,113 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Service < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :service
+ @service_name = name
+ @enabled = nil
+ @running = nil
+ @pattern = service_name
+ @start_command = nil
+ @stop_command = nil
+ @status_command = nil
+ @restart_command = nil
+ @action = "none"
+ @supports = { :restart => false, :status => false }
+ @allowed_actions.push(:enable, :disable, :start, :stop)
+ end
+
+ def service_name(arg=nil)
+ set_or_return(
+ :service_name,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # regex for match against ps -ef when !supports[:has_status] && status == nil
+ def pattern(arg=nil)
+ set_or_return(
+ :pattern,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # command to call to start service
+ def start_command(arg=nil)
+ set_or_return(
+ :start_command,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # command to call to stop service
+ def stop_command(arg=nil)
+ set_or_return(
+ :stop_command,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # command to call to get status of service
+ def status_command(arg=nil)
+ set_or_return(
+ :status_command,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # command to call to restart service
+ def restart_command(arg=nil)
+ set_or_return(
+ :restart_command,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ # if the service is enabled or not
+ def enabled(arg=nil)
+ set_or_return(
+ :enabled,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+ # if the service is running or not
+ def running(arg=nil)
+ set_or_return(
+ :running,
+ arg,
+ :kind_of => [ TrueClass, FalseClass ]
+ )
+ end
+
+
+ end
+ end
+end
diff --git a/chef/lib/chef/resource/sysctl.rb b/chef/lib/chef/resource/sysctl.rb
new file mode 100644
index 0000000000..203ad72425
--- /dev/null
+++ b/chef/lib/chef/resource/sysctl.rb
@@ -0,0 +1,42 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Sysctl < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :sysctl
+ @action = "set"
+ @name = name
+ @value = nil
+ @variables = Hash.new
+ end
+
+ def value(args=nil)
+ set_or_return(
+ :value,
+ args,
+ :kind_of => String
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/template.rb b/chef/lib/chef/resource/template.rb
new file mode 100644
index 0000000000..5f2512a274
--- /dev/null
+++ b/chef/lib/chef/resource/template.rb
@@ -0,0 +1,49 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class Template < Chef::Resource::File
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :template
+ @action = "create"
+ @source = nil
+ @variables = Hash.new
+ end
+
+ def source(file=nil)
+ set_or_return(
+ :source,
+ file,
+ :kind_of => [ String ]
+ )
+ end
+
+ def variables(args=nil)
+ set_or_return(
+ :variables,
+ args,
+ :kind_of => [ Hash ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource/user.rb b/chef/lib/chef/resource/user.rb
new file mode 100644
index 0000000000..1702753408
--- /dev/null
+++ b/chef/lib/chef/resource/user.rb
@@ -0,0 +1,96 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class Resource
+ class User < Chef::Resource
+
+ def initialize(name, collection=nil, node=nil)
+ super(name, collection, node)
+ @resource_name = :user
+ @username = name
+ @comment = nil
+ @uid = nil
+ @gid = nil
+ @home = nil
+ @shell = nil
+ @password = nil
+ @action = :create
+ @supports = { :manage_home => false }
+ @allowed_actions.push(:create, :remove, :modify, :manage, :lock, :unlock)
+ end
+
+ def username(arg=nil)
+ set_or_return(
+ :username,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def comment(arg=nil)
+ set_or_return(
+ :comment,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def uid(arg=nil)
+ set_or_return(
+ :uid,
+ arg,
+ :kind_of => [ String, Integer ]
+ )
+ end
+
+ def gid(arg=nil)
+ set_or_return(
+ :gid,
+ arg,
+ :kind_of => [ String, Integer ]
+ )
+ end
+
+ def home(arg=nil)
+ set_or_return(
+ :home,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def shell(arg=nil)
+ set_or_return(
+ :shell,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ def password(arg=nil)
+ set_or_return(
+ :password,
+ arg,
+ :kind_of => [ String ]
+ )
+ end
+
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource_collection.rb b/chef/lib/chef/resource_collection.rb
new file mode 100644
index 0000000000..5278b15c72
--- /dev/null
+++ b/chef/lib/chef/resource_collection.rb
@@ -0,0 +1,174 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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.
+#
+
+class Chef
+ class ResourceCollection
+ include Enumerable
+
+ def initialize
+ @resources = Array.new
+ @resources_by_name = Hash.new
+ end
+
+ def [](index)
+ @resources[index]
+ end
+
+ def []=(index, arg)
+ is_chef_resource(arg)
+ @resources[index] = arg
+ @resources_by_name[arg.to_s] = index
+ end
+
+ def <<(*args)
+ args.flatten.each do |a|
+ is_chef_resource(a)
+ @resources << a
+ @resources_by_name[a.to_s] = @resources.length - 1
+ end
+ end
+
+ def push(*args)
+ args.flatten.each do |a|
+ is_chef_resource(a)
+ @resources.push(a)
+ @resources_by_name[a.to_s] = @resources.length - 1
+ end
+ end
+
+ def each
+ @resources.each do |r|
+ yield r
+ end
+ end
+
+ def each_index
+ @resources.each_index do |i|
+ yield i
+ end
+ end
+
+ def lookup(resource)
+ lookup_by = nil
+ if resource.kind_of?(Chef::Resource)
+ lookup_by = resource.to_s
+ elsif resource.kind_of?(String)
+ lookup_by = resource
+ else
+ raise ArgumentError, "Must pass a Chef::Resource or String to lookup"
+ end
+ res = @resources_by_name[lookup_by]
+ unless res
+ raise ArgumentError, "Cannot find a resource matching #{lookup_by} (did you define it first?)"
+ end
+ @resources[res]
+ end
+
+ # Find existing resources by searching the list of existing resources. Possible
+ # forms are:
+ #
+ # resources(:file => "foobar")
+ # resources(:file => [ "foobar", "baz" ])
+ # resources("file[foobar]", "file[baz]")
+ # resources("file[foobar,baz]")
+ #
+ # Returns the matching resource, or an Array of matching resources.
+ #
+ # Raises an ArgumentError if you feed it bad lookup information
+ # Raises a Runtime Error if it can't find the resources you are looking for.
+ def resources(*args)
+ results = Array.new
+ args.each do |arg|
+ case arg
+ when Hash
+ results << find_resource_by_hash(arg)
+ when String
+ results << find_resource_by_string(arg)
+ else
+ raise ArgumentError, "resources takes arguments as a hash or strings!"
+ end
+ end
+ flat_results = results.flatten
+ flat_results.length == 1 ? flat_results[0] : flat_results
+ end
+
+ # Serialize this object as a hash
+ def to_json(*a)
+ instance_vars = Hash.new
+ self.instance_variables.each do |iv|
+ instance_vars[iv] = self.instance_variable_get(iv)
+ end
+ results = {
+ 'json_class' => self.class.name,
+ 'instance_vars' => instance_vars
+ }
+ results.to_json(*a)
+ end
+
+ def self.json_create(o)
+ collection = self.new()
+ o["instance_vars"].each do |k,v|
+ collection.instance_variable_set(k.to_sym, v)
+ end
+ collection
+ end
+
+ private
+
+ def find_resource_by_hash(arg)
+ results = Array.new
+ arg.each do |resource_name, name_list|
+ names = name_list.kind_of?(Array) ? name_list : [ name_list ]
+ names.each do |name|
+ res_name = "#{resource_name.to_s}[#{name}]"
+ results << lookup(res_name)
+ end
+ end
+ return results
+ end
+
+ def find_resource_by_string(arg)
+ results = Array.new
+ case arg
+ when /^(.+)\[(.+?),(.+)\]$/
+ resource_type = $1
+ arg =~ /^.+\[(.+)\]$/
+ resource_list = $1
+ resource_list.split(",").each do |name|
+ resource_name = "#{resource_type}[#{name}]"
+ results << lookup(resource_name)
+ end
+ when /^(.+)\[(.+)\]$/
+ resource_type = $1
+ name = $2
+ resource_name = "#{resource_type}[#{name}]"
+ results << lookup(resource_name)
+ else
+ raise ArgumentError, "You must have a string like resource_type[name]!"
+ end
+ return results
+ end
+
+ def is_chef_resource(arg)
+ unless arg.kind_of?(Chef::Resource)
+ raise ArgumentError, "Members must be Chef::Resource's"
+ end
+ true
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/resource_definition.rb b/chef/lib/chef/resource_definition.rb
new file mode 100644
index 0000000000..9a0a9cb09a
--- /dev/null
+++ b/chef/lib/chef/resource_definition.rb
@@ -0,0 +1,67 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "from_file")
+require File.join(File.dirname(__FILE__), "mixin", "params_validate")
+
+
+class Chef
+ class ResourceDefinition
+
+ include Chef::Mixin::FromFile
+ include Chef::Mixin::ParamsValidate
+
+ attr_accessor :name, :params, :recipe
+
+ def initialize
+ @name = nil
+ @params = Hash.new
+ @recipe = nil
+ end
+
+ def define(resource_name, prototype_params=nil, &block)
+ unless resource_name.kind_of?(Symbol)
+ raise ArgumentError, "You must use a symbol when defining a new resource!"
+ end
+ @name = resource_name
+ if prototype_params
+ unless prototype_params.kind_of?(Hash)
+ raise ArgumentError, "You must pass a hash as the prototype parameters for a definition."
+ end
+ @params = prototype_params
+ end
+ if Kernel.block_given?
+ @recipe = block
+ else
+ raise ArgumentError, "You must pass a block to a definition."
+ end
+ true
+ end
+
+ # When we do the resource definition, we're really just setting new values for
+ # the paramaters we prototyped at the top. This method missing is as simple as
+ # it gets.
+ def method_missing(symbol, *args)
+ @params[symbol] = args.length == 1 ? args[0] : args
+ end
+
+ def to_s
+ "#{name.to_s}"
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/rest.rb b/chef/lib/chef/rest.rb
new file mode 100644
index 0000000000..b78df6c35f
--- /dev/null
+++ b/chef/lib/chef/rest.rb
@@ -0,0 +1,150 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require 'net/https'
+require 'uri'
+require 'json'
+require 'tempfile'
+
+class Chef
+ class REST
+
+ def initialize(url)
+ @url = url
+ @cookies = Hash.new
+ end
+
+ # Send an HTTP GET request to the path
+ #
+ # === Parameters
+ # path:: The path to GET
+ # raw:: Whether you want the raw body returned, or JSON inflated. Defaults
+ # to JSON inflated.
+ def get_rest(path, raw=false)
+ run_request(:GET, create_url(path), false, 10, raw)
+ end
+
+ # Send an HTTP DELETE request to the path
+ def delete_rest(path)
+ run_request(:DELETE, create_url(path))
+ end
+
+ # Send an HTTP POST request to the path
+ def post_rest(path, json)
+ run_request(:POST, create_url(path), json)
+ end
+
+ # Send an HTTP PUT request to the path
+ def put_rest(path, json)
+ run_request(:PUT, create_url(path), json)
+ end
+
+ def create_url(path)
+ if path =~ /^(http|https):\/\//
+ URI.parse(path)
+ else
+ URI.parse("#{@url}/#{path}")
+ end
+ end
+
+ # Actually run an HTTP request. First argument is the HTTP method,
+ # which should be one of :GET, :PUT, :POST or :DELETE. Next is the
+ # URL, then an object to include in the body (which will be converted with
+ # .to_json) and finally, the limit of HTTP Redirects to follow (10).
+ #
+ # Typically, you won't use this method -- instead, you'll use one of
+ # the helper methods (get_rest, post_rest, etc.)
+ #
+ # Will return the body of the response on success.
+ def run_request(method, url, data=false, limit=10, raw=false)
+ raise ArgumentError, 'HTTP redirect too deep' if limit == 0
+
+ http = Net::HTTP.new(url.host, url.port)
+ if url.scheme == "https"
+ http.use_ssl = true
+ if Chef::Config[:ssl_verify_mode] == :verify_none
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ end
+ end
+ http.read_timeout = Chef::Config[:rest_timeout]
+ headers = Hash.new
+ unless raw
+ headers = {
+ 'Accept' => "application/json",
+ }
+ end
+ if @cookies["#{url.host}:#{url.port}"]
+ headers['Cookie'] = @cookies["#{url.host}:#{url.port}"]
+ end
+ req = nil
+ case method
+ when :GET
+ req_path = "#{url.path}"
+ req_path << "?#{url.query}" if url.query
+ req = Net::HTTP::Get.new(req_path, headers)
+ when :POST
+ headers["Content-Type"] = 'application/json' if data
+ req = Net::HTTP::Post.new(url.path, headers)
+ req.body = data.to_json if data
+ when :PUT
+ headers["Content-Type"] = 'application/json' if data
+ req = Net::HTTP::Put.new(url.path, headers)
+ req.body = data.to_json if data
+ when :DELETE
+ req_path = "#{url.path}"
+ req_path << "?#{url.query}" if url.query
+ req = Net::HTTP::Delete.new(req_path, headers)
+ else
+ raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method"
+ end
+ Chef::Log.debug("Sending HTTP Request via #{req.method} to #{req.path}")
+ res = http.request(req)
+
+ Chef::Log.debug("HTTP request headers: #{req.to_hash.inspect} ")
+
+ Chef::Log.debug("HTTP response headers: #{res.to_hash.inspect} ")
+
+ if res.kind_of?(Net::HTTPSuccess)
+ if res['set-cookie']
+ @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
+ end
+ if res['content-type'] =~ /json/
+ JSON.parse(res.body)
+ else
+ if raw
+ tf = Tempfile.new("chef-rest")
+ tf.print(res.body)
+ tf.close
+ tf
+ else
+ res.body
+ end
+ end
+ elsif res.kind_of?(Net::HTTPFound)
+ if res['set-cookie']
+ @cookies["#{url.host}:#{url.port}"] = res['set-cookie']
+ end
+ run_request(:GET, create_url(res['location']), false, limit - 1, raw)
+ else
+ res.error!
+ end
+ end
+
+ end
+end
diff --git a/chef/lib/chef/runner.rb b/chef/lib/chef/runner.rb
new file mode 100644
index 0000000000..b943ef5ec4
--- /dev/null
+++ b/chef/lib/chef/runner.rb
@@ -0,0 +1,100 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+
+class Chef
+ class Runner
+
+ include Chef::Mixin::ParamsValidate
+
+ def initialize(node, collection)
+ validate(
+ {
+ :node => node,
+ :collection => collection,
+ },
+ {
+ :node => {
+ :kind_of => Chef::Node,
+ },
+ :collection => {
+ :kind_of => Chef::ResourceCollection,
+ },
+ }
+ )
+ @node = node
+ @collection = collection
+ end
+
+ def build_provider(resource)
+ provider_klass = resource.provider
+ if provider_klass == nil
+ provider_klass = Chef::Platform.find_provider_for_node(@node, resource)
+ end
+ Chef::Log.debug("#{resource} using #{provider_klass.to_s}")
+ provider = provider_klass.new(@node, resource)
+ provider.load_current_resource
+ provider
+ end
+
+ def converge
+ start_time = Time.now
+ Chef::Log.info("Starting Chef Run")
+ delayed_actions = Array.new
+
+ @collection.each do |resource|
+ begin
+ Chef::Log.debug("Processing #{resource}")
+ action_list = resource.action.kind_of?(Array) ? resource.action : [ resource.action ]
+ action_list.each do |ra|
+ provider = build_provider(resource)
+ provider.send("action_#{ra}")
+ if resource.updated
+ resource.actions.each_key do |action|
+ if resource.actions[action].has_key?(:immediate)
+ resource.actions[action][:immediate].each do |r|
+ Chef::Log.info("#{resource} sending #{action} action to #{r} (immediate)")
+ build_provider(r).send("action_#{action}")
+ end
+ end
+ if resource.actions[action].has_key?(:delayed)
+ resource.actions[action][:delayed].each do |r|
+ delayed_actions << lambda {
+ Chef::Log.info("#{resource} sending #{action} action to #{r} (delayed)")
+ build_provider(r).send("action_#{action}")
+ }
+ end
+ end
+ end
+ end
+ end
+ rescue => e
+ Chef::Log.error("#{resource} (#{resource.source_line}) had an error:")
+ raise
+ end
+ end
+
+ # Run all our :delayed actions
+ delayed_actions.each { |da| da.call }
+ end_time = Time.now
+ Chef::Log.info("Chef Run complete in #{end_time - start_time} seconds")
+ true
+ end
+ end
+end
diff --git a/chef/lib/chef/search.rb b/chef/lib/chef/search.rb
new file mode 100644
index 0000000000..229f738399
--- /dev/null
+++ b/chef/lib/chef/search.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require 'ferret'
+
+class Chef
+ class Search
+
+ attr_reader :index
+
+ def initialize
+ @index = Ferret::Index::Index.new(:path => Chef::Config[:search_index_path])
+ end
+
+ def search(type, query, &block)
+ search_query = build_search_query(type, query)
+ start_time = Time.now
+ result = Array.new
+
+ if Kernel.block_given?
+ result = @index.search_each(search_query, :limit => :all) do |id, score|
+ block.call(build_hash(@index.doc(id)))
+ end
+ else
+ @index.search_each(search_query, :limit => :all) do |id, score|
+ result << build_hash(@index.doc(id))
+ end
+ end
+ Chef::Log.debug("Search #{search_query} complete in #{Time.now - start_time} seconds")
+ result
+ end
+
+ def list_indexes
+ indexes = Hash.new
+ @index.search_each("index_name:*", :limit => :all) do |id, score|
+ indexes[@index.doc(id)["index_name"]] = true
+ end
+ indexes.keys
+ end
+
+ def has_index?(index)
+ list_indexes.detect { |i| i == index }
+ end
+
+ private
+ def build_search_query(type, query)
+ "index_name:#{type} AND (#{query})"
+ end
+
+ def build_hash(doc)
+ result = Hash.new
+ doc.fields.each do |f|
+ result[f] = doc[f]
+ end
+ result
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/search_index.rb b/chef/lib/chef/search_index.rb
new file mode 100644
index 0000000000..932a451999
--- /dev/null
+++ b/chef/lib/chef/search_index.rb
@@ -0,0 +1,80 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "mixin", "params_validate")
+require 'ferret'
+
+class Chef
+ class SearchIndex
+
+ attr_reader :index
+
+ def initialize
+ @index = Ferret::Index::Index.new(
+ :path => Chef::Config[:search_index_path],
+ :key => [ :id ]
+ )
+ end
+
+ def add(new_object)
+ index_hash = create_index_object(new_object)
+ Chef::Log.debug("Indexing #{index_hash[:index_name]} with #{index_hash.inspect}")
+ @index.add_document(index_hash)
+ end
+
+ def create_index_object(new_object)
+ index_hash = nil
+
+ if new_object.respond_to?(:to_index)
+ index_hash = new_object.to_index
+ elsif new_object.kind_of?(Hash)
+ index_hash = new_object
+ else
+ raise Chef::Exception::SearchIndex, "Cannot transform argument to a Hash!"
+ end
+
+ unless index_hash.has_key?(:index_name) || index_hash.has_key?("index_name")
+ raise Chef::Exception::SearchIndex, "Cannot index without an index_name key: #{index_hash.inspect}"
+ end
+
+ unless index_hash.has_key?(:id) || index_hash.has_key?("id")
+ raise Chef::Exception::SearchIndex, "Cannot index without an id key: #{index_hash.inspect}"
+ end
+
+ index_hash.each do |k,v|
+ unless k.kind_of?(Symbol)
+ index_hash[k.to_sym] = v
+ index_hash.delete(k)
+ end
+ end
+
+ index_hash
+ end
+
+ def delete(index_obj)
+ to_delete = create_index_object(index_obj)
+ Chef::Log.debug("Removing #{to_delete.inspect} from the #{to_delete[:index_name]} index")
+ @index.delete(to_delete[:id])
+ end
+
+ def commit
+ @index.commit
+ end
+
+ end
+end
diff --git a/chef/log/chef-server.log b/chef/log/chef-server.log
new file mode 100644
index 0000000000..eabfde15a0
--- /dev/null
+++ b/chef/log/chef-server.log
@@ -0,0 +1,9 @@
+Mon, 16 Jun 2008 01:13:56 GMT ~ info ~ Logfile created
+ ~ Compiling routes...
+ ~ Using 'share-nothing' cookie sessions (4kb limit per client)
+ ~ Using Mongrel adapter
+ ~ Compiling routes...
+ ~ Using 'share-nothing' cookie sessions (4kb limit per client)
+ ~ Using Mongrel adapter
+ ~ Using 'share-nothing' cookie sessions (4kb limit per client)
+ ~ Using Mongrel adapter
diff --git a/chef/log/merb.main.pid b/chef/log/merb.main.pid
new file mode 100644
index 0000000000..0eb419a8f1
--- /dev/null
+++ b/chef/log/merb.main.pid
@@ -0,0 +1 @@
+12210 \ No newline at end of file
diff --git a/chef/log/merb_test.log b/chef/log/merb_test.log
new file mode 100644
index 0000000000..dae0e5d9dd
--- /dev/null
+++ b/chef/log/merb_test.log
@@ -0,0 +1,7 @@
+Tue, 03 Jun 2008 04:33:52 GMT ~ info ~ Logfile created
+ ~ Not Using Sessions
+ ~ Not Using Sessions
+ ~ Not Using Sessions
+ ~ Not Using Sessions
+ ~ Not Using Sessions
+ ~ Not Using Sessions
diff --git a/chef/log/stompserver.pid b/chef/log/stompserver.pid
new file mode 100644
index 0000000000..20e502d586
--- /dev/null
+++ b/chef/log/stompserver.pid
@@ -0,0 +1 @@
+12157 \ No newline at end of file
diff --git a/chef/pkg/chef-0.0.1.gem b/chef/pkg/chef-0.0.1.gem
new file mode 100644
index 0000000000..c090729e12
--- /dev/null
+++ b/chef/pkg/chef-0.0.1.gem
Binary files differ
diff --git a/chef/spec/chef_server/controllers/log/merb_test.log b/chef/spec/chef_server/controllers/log/merb_test.log
new file mode 100644
index 0000000000..86fbe4b9cb
--- /dev/null
+++ b/chef/spec/chef_server/controllers/log/merb_test.log
@@ -0,0 +1,4 @@
+Tue, 27 May 2008 00:52:34 GMT ~ info ~ Logfile created
+ ~ Not Using Sessions
+ ~ Not Using Sessions
+ ~ Not Using Sessions
diff --git a/chef/spec/chef_server/controllers/nodes_spec.rb b/chef/spec/chef_server/controllers/nodes_spec.rb
new file mode 100644
index 0000000000..65c2df301a
--- /dev/null
+++ b/chef/spec/chef_server/controllers/nodes_spec.rb
@@ -0,0 +1,232 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
+#
+# describe Nodes, "index action" do
+# it "should get a list of all the nodes" do
+# Chef::Node.should_receive(:list).and_return(["one"])
+# dispatch_to(Nodes, :index) do |c|
+# c.stub!(:display)
+# end
+# end
+#
+# it "should send a list of nodes to display" do
+# Chef::Node.stub!(:list).and_return(["one"])
+# dispatch_to(Nodes, :index) do |c|
+# c.should_receive(:display).with(["one"])
+# end
+# end
+# end
+#
+# describe Nodes, "show action" do
+# it "should load a node from the filestore based on the id" do
+# node = stub("Node", :null_object => true)
+# Chef::Node.should_receive(:load).with("bond").once.and_return(node)
+# dispatch_to(Nodes, :show, { :id => "bond" }) do |c|
+# c.should_receive(:display).with(node).once.and_return(true)
+# end
+# end
+#
+# it "should return 200 on a well formed request" do
+# node = stub("Node", :null_object => true)
+# Chef::Node.should_receive(:load).with("bond").once.and_return(node)
+# controller = dispatch_to(Nodes, :show, { :id => "bond" }) do |c|
+# c.stub!(:display)
+# end
+# controller.status.should eql(200)
+# end
+#
+# it "should raise a BadRequest if the id is not found" do
+# Chef::Node.should_receive(:load).with("bond").once.and_raise(RuntimeError)
+# lambda {
+# dispatch_to(Nodes, :show, { :id => "bond" })
+# }.should raise_error(Merb::ControllerExceptions::BadRequest)
+# end
+# end
+#
+# describe Nodes, "create action" do
+# it "should create a node from an inflated object" do
+# mnode = mock("Node", :null_object => true)
+# mnode.stub!(:name).and_return("bond")
+# mnode.should_receive(:save).once.and_return(true)
+# controller = dispatch_to(Nodes, :create) do |c|
+# c.stub!(:params).and_return({ "inflated_object" => mnode })
+# c.stub!(:session).and_return({
+# :openid => 'http://localhost/openid/server/node/bond',
+# :level => :node,
+# :node_name => "bond",
+# })
+# c.stub!(:display)
+# end
+# controller.status.should eql(202)
+# end
+#
+# it "should raise an exception if it cannot inflate an object" do
+# lambda {
+# dispatch_to(Nodes, :create) do |c|
+# c.stub!(:params).and_return({ })
+# end
+# }.should raise_error(Merb::Controller::BadRequest)
+# end
+# end
+#
+# describe Nodes, "update action" do
+# it "should update a node from an inflated object" do
+# mnode = mock("Node", :null_object => true)
+# mnode.stub!(:name).and_return("one")
+# Chef::FileStore.should_receive(:store).with("node", "one", mnode).once.and_return(true)
+# controller = dispatch_to(Nodes, :update, { :id => "one" }) do |c|
+# c.stub!(:session).and_return({
+# :openid => 'http://localhost/openid/server/node/one',
+# :level => :node,
+# :node_name => "one",
+# })
+# c.stub!(:params).and_return({ "inflated_object" => mnode })
+# c.stub!(:display)
+# end
+# controller.status.should eql(202)
+# end
+#
+# it "should raise an exception if it cannot inflate an object" do
+# lambda { dispatch_to(Nodes, :update) }.should raise_error(Merb::Controller::BadRequest)
+# end
+# end
+#
+# describe Nodes, "destroy action" do
+# def do_destroy
+# dispatch_to(Nodes, :destroy, { :id => "one" }) do |c|
+# c.stub!(:display)
+# end
+# end
+#
+# it "should load the node it's about to destroy from the filestore" do
+# mnode = stub("Node", :null_object => true)
+# Chef::FileStore.should_receive(:load).with("node", "one").once.and_return(mnode)
+# Chef::FileStore.stub!(:delete)
+# do_destroy
+# end
+#
+# it "should raise an exception if it cannot find the node to destroy" do
+# Chef::FileStore.should_receive(:load).with("node", "one").once.and_raise(RuntimeError)
+# lambda { do_destroy }.should raise_error(Merb::Controller::BadRequest)
+# end
+#
+# it "should remove the node from the filestore" do
+# mnode = stub("Node", :null_object => true)
+# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode)
+# Chef::FileStore.should_receive(:delete).with("node", "one")
+# do_destroy
+# end
+#
+# it "should remove the node from the search index" do
+# mnode = stub("Node", :null_object => true)
+# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode)
+# Chef::FileStore.stub!(:delete)
+# do_destroy
+# end
+#
+# it "should return the node it just deleted" do
+# mnode = stub("Node", :null_object => true)
+# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode)
+# Chef::FileStore.stub!(:delete)
+# dispatch_to(Nodes, :destroy, { :id => "one" }) do |c|
+# c.should_receive(:display).once.with(mnode)
+# end
+# end
+#
+# it "should return a status of 202" do
+# mnode = stub("Node", :null_object => true)
+# Chef::FileStore.stub!(:load).with("node", "one").and_return(mnode)
+# Chef::FileStore.stub!(:delete)
+# controller = do_destroy
+# controller.status.should eql(202)
+# end
+# end
+#
+# describe Nodes, "compile action" do
+# before(:each) do
+# @compile = stub("Compile", :null_object => true)
+# @node = stub("Node", :null_object => true)
+# @node.stub!(:[]).and_return(true)
+# @node.stub!(:[]=).and_return(true)
+# @node.stub!(:recipes).and_return([])
+# @compile.stub!(:load_definitions).and_return(true)
+# @compile.stub!(:load_recipes).and_return(true)
+# @compile.stub!(:collection).and_return([])
+# @compile.stub!(:node, @node)
+# @compile.stub!(:load_node).and_return(true)
+# @stored_node = stub("Stored Node", :null_object => true)
+# end
+#
+# def do_compile
+# Chef::FileStore.stub!(:store).and_return(true)
+# Chef::FileStore.stub!(:load).and_return(@stored_node)
+# Chef::Compile.stub!(:new).and_return(@compile)
+# dispatch_to(Nodes, :compile, { :id => "one" }) do |c|
+# c.stub!(:display)
+# end
+# end
+#
+# it "should load the node from the node resource" do
+# @compile.should_receive(:load_node).with("one").and_return(true)
+# do_compile
+# end
+#
+# it "should merge the data with the currently stored node" do
+# node1 = Chef::Node.new
+# node1.name "adam"
+# node1.music "crowe"
+# node1.recipes << "monkey"
+# @compile.stub!(:node).and_return(node1)
+# @stored_node = Chef::Node.new
+# @stored_node.name "adam"
+# @stored_node.music "crown"
+# @stored_node.woot "woot"
+# @stored_node.recipes << "monkeysoup"
+# do_compile
+# node1.name.should eql("adam")
+# node1.music.should eql("crown")
+# node1.woot.should eql("woot")
+# node1.recipes.should eql([ "monkey", "monkeysoup" ])
+# end
+#
+# it "should load definitions" do
+# @compile.should_receive(:load_definitions)
+# do_compile
+# end
+#
+# it "should load recipes" do
+# @compile.should_receive(:load_recipes)
+# do_compile
+# end
+#
+# it "should display the collection and node object" do
+# Chef::FileStore.stub!(:load).and_return(@stored_node)
+# Chef::Compile.stub!(:new).and_return(@compile)
+# dispatch_to(Nodes, :compile, { :id => "one" }) do |c|
+# c.should_receive(:display).with({ :collection => [], :node => nil })
+# end
+# end
+#
+# it "should return 200" do
+# controller = do_compile
+# controller.status.should eql(200)
+# end
+#
+# end \ No newline at end of file
diff --git a/chef/spec/chef_server/controllers/openid_consumer_spec.rb b/chef/spec/chef_server/controllers/openid_consumer_spec.rb
new file mode 100644
index 0000000000..0156fd8d49
--- /dev/null
+++ b/chef/spec/chef_server/controllers/openid_consumer_spec.rb
@@ -0,0 +1,47 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
+#
+# describe OpenidConsumer, "check_valid_openid_provider method" do
+# it "should confirm the openid provider if the openid_providers config is nil" do
+# Chef::Config[:openid_providers] = nil
+# c = OpenidConsumer.new(true)
+# c.send(:check_valid_openid_provider, "monkeyid").should eql(true)
+# end
+#
+# it "should return true if the openid provider is in openid_providers list" do
+# Chef::Config[:openid_providers] = [ 'monkeyid' ]
+# c = OpenidConsumer.new(true)
+# c.send(:check_valid_openid_provider, "monkeyid").should eql(true)
+# end
+#
+# it "should return true if the openid provider is in openid_providers list with http://" do
+# Chef::Config[:openid_providers] = [ 'monkeyid' ]
+# c = OpenidConsumer.new(true)
+# c.send(:check_valid_openid_provider, "http://monkeyid").should eql(true)
+# end
+#
+# it "should raise an exception if the openid provider is not in openid_providers list" do
+# Chef::Config[:openid_providers] = [ 'monkeyid' ]
+# c = OpenidConsumer.new(true)
+# lambda {
+# c.send(:check_valid_openid_provider, "monkey")
+# }.should raise_error(Merb::Controller::Unauthorized)
+# end
+# end \ No newline at end of file
diff --git a/chef/spec/chef_server/controllers/openid_register_spec.rb b/chef/spec/chef_server/controllers/openid_register_spec.rb
new file mode 100644
index 0000000000..c883715fc6
--- /dev/null
+++ b/chef/spec/chef_server/controllers/openid_register_spec.rb
@@ -0,0 +1,111 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.join(File.dirname(__FILE__), "..", 'spec_helper.rb')
+#
+# describe OpenidRegister, "index action" do
+# it "should get a list of all registered nodes" do
+# Chef::OpenIDRegistration.should_receive(:list).with(true).and_return(["one"])
+# dispatch_to(OpenidRegister, :index) do |c|
+# c.stub!(:display)
+# end
+# end
+# end
+#
+# describe OpenidRegister, "show action" do
+# it "should raise a 404 if the nodes registration is not found" do
+# Chef::OpenIDRegistration.should_receive(:load).with("foo").and_raise(RuntimeError)
+# lambda {
+# dispatch_to(OpenidRegister, :show, { :id => "foo" })
+# }.should raise_error(Merb::ControllerExceptions::NotFound)
+# end
+#
+# it "should call display on the node registration" do
+# Chef::OpenIDRegistration.stub!(:load).and_return(true)
+# dispatch_to(OpenidRegister, :show, { :id => "foo" }) do |c|
+# c.should_receive(:display).with(true)
+# end
+# end
+# end
+#
+# describe OpenidRegister, "create action" do
+# def do_create
+# dispatch_to(OpenidRegister, :create, { :id => "foo", :password => "beck" }) do |c|
+# c.stub!(:display)
+# end
+# end
+#
+# it "should require an id to register" do
+# lambda {
+# dispatch_to(OpenidRegister, :create, { :password => "beck" })
+# }.should raise_error(Merb::ControllerExceptions::BadRequest)
+# end
+#
+# it "should require a password to register" do
+# lambda {
+# dispatch_to(OpenidRegister, :create, { :id => "foo" })
+# }.should raise_error(Merb::ControllerExceptions::BadRequest)
+# end
+#
+# it "should return 400 if a node is already registered" do
+# Chef::OpenIDRegistration.should_receive(:has_key?).with("foo").and_return(true)
+# lambda {
+# dispatch_to(OpenidRegister, :create, { :id => "foo", :password => "beck" })
+# }.should raise_error(Merb::ControllerExceptions::BadRequest)
+# end
+#
+# it "should store the registration in a new Chef::OpenIDRegistration" do
+# mock_reg = mock("Chef::OpenIDRegistration", :null_object => true)
+# mock_reg.should_receive(:name=).with("foo").and_return(true)
+# mock_reg.should_receive(:set_password).with("beck").and_return(true)
+# mock_reg.should_receive(:save).and_return(true)
+# Chef::OpenIDRegistration.stub!(:has_key?).and_return(false)
+# Chef::OpenIDRegistration.should_receive(:new).and_return(mock_reg)
+# do_create
+# end
+# end
+#
+# describe OpenidRegister, "update action" do
+# it "should raise a 400 error" do
+# lambda {
+# dispatch_to(OpenidRegister, :update)
+# }
+# end
+# end
+#
+# describe OpenidRegister, "destroy action" do
+# def do_destroy
+# dispatch_to(OpenidRegister, :destroy, { :id => "foo" }) do |c|
+# c.stub!(:display)
+# end
+# end
+#
+# it "should return 400 if it cannot find the registration" do
+# Chef::OpenIDRegistration.should_receive(:load).and_raise(ArgumentError)
+# lambda {
+# do_destroy
+# }.should raise_error(Merb::ControllerExceptions::BadRequest)
+# end
+#
+# it "should delete the registration from the store" do
+# mock_reg = mock("OpenIDRegistration")
+# mock_reg.should_receive(:destroy).and_return(true)
+# Chef::OpenIDRegistration.should_receive(:load).with("foo").and_return(mock_reg)
+# do_destroy
+# end
+# end \ No newline at end of file
diff --git a/chef/spec/chef_server/log/merb_test.log b/chef/spec/chef_server/log/merb_test.log
new file mode 100644
index 0000000000..b2aa15df9a
--- /dev/null
+++ b/chef/spec/chef_server/log/merb_test.log
@@ -0,0 +1,4 @@
+Mon, 16 Jun 2008 02:08:02 GMT ~ info ~ Logfile created
+ ~ Not Using Sessions
+ ~ Not Using Sessions
+ ~ Not Using Sessions
diff --git a/chef/spec/chef_server/spec.opts b/chef/spec/chef_server/spec.opts
new file mode 100644
index 0000000000..3a2ff550a2
--- /dev/null
+++ b/chef/spec/chef_server/spec.opts
@@ -0,0 +1,4 @@
+--color
+--loadby
+mtime
+
diff --git a/chef/spec/chef_server/spec_helper.rb b/chef/spec/chef_server/spec_helper.rb
new file mode 100644
index 0000000000..90c1e0af7a
--- /dev/null
+++ b/chef/spec/chef_server/spec_helper.rb
@@ -0,0 +1,16 @@
+require 'rubygems'
+require 'merb-core'
+require 'spec' # Satiates Autotest and anyone else not using the Rake tasks
+
+Merb.start_environment(
+ :testing => true,
+ :adapter => 'runner',
+ :environment => ENV['MERB_ENV'] || 'test',
+ :init_file => File.join(File.dirname(__FILE__), "..", "..", "lib", "chef_server", "init.rb")
+)
+
+Spec::Runner.configure do |config|
+ config.include(Merb::Test::ViewHelper)
+ config.include(Merb::Test::RouteHelper)
+ config.include(Merb::Test::ControllerHelper)
+end
diff --git a/chef/spec/data/bad-config.rb b/chef/spec/data/bad-config.rb
new file mode 100644
index 0000000000..5477a69366
--- /dev/null
+++ b/chef/spec/data/bad-config.rb
@@ -0,0 +1 @@
+monkey_soup("tastes nice") \ No newline at end of file
diff --git a/chef/spec/data/compile/cookbooks/test/attributes/george.rb b/chef/spec/data/compile/cookbooks/test/attributes/george.rb
new file mode 100644
index 0000000000..5df9567761
--- /dev/null
+++ b/chef/spec/data/compile/cookbooks/test/attributes/george.rb
@@ -0,0 +1 @@
+george "washington" \ No newline at end of file
diff --git a/chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb b/chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb
new file mode 100644
index 0000000000..a49b53348c
--- /dev/null
+++ b/chef/spec/data/compile/cookbooks/test/definitions/new_cat.rb
@@ -0,0 +1,5 @@
+define :new_cat, :is_pretty => true do
+ cat "#{params[:name]}" do
+ pretty_kitty params[:is_pretty]
+ end
+end
diff --git a/chef/spec/data/compile/cookbooks/test/recipes/default.rb b/chef/spec/data/compile/cookbooks/test/recipes/default.rb
new file mode 100644
index 0000000000..d769dc826d
--- /dev/null
+++ b/chef/spec/data/compile/cookbooks/test/recipes/default.rb
@@ -0,0 +1,5 @@
+
+cat "einstein" do
+ pretty_kitty true
+end
+
diff --git a/chef/spec/data/compile/cookbooks/test/recipes/one.rb b/chef/spec/data/compile/cookbooks/test/recipes/one.rb
new file mode 100644
index 0000000000..7795cc1d4a
--- /dev/null
+++ b/chef/spec/data/compile/cookbooks/test/recipes/one.rb
@@ -0,0 +1,7 @@
+cat "loulou" do
+ pretty_kitty true
+end
+
+new_cat "birthday" do
+ pretty_kitty false
+end
diff --git a/chef/spec/data/compile/cookbooks/test/recipes/two.rb b/chef/spec/data/compile/cookbooks/test/recipes/two.rb
new file mode 100644
index 0000000000..01def1b2ac
--- /dev/null
+++ b/chef/spec/data/compile/cookbooks/test/recipes/two.rb
@@ -0,0 +1,7 @@
+cat "peanut" do
+ pretty_kitty true
+end
+
+new_cat "fat peanut" do
+ pretty_kitty false
+end
diff --git a/chef/spec/data/compile/nodes/compile.rb b/chef/spec/data/compile/nodes/compile.rb
new file mode 100644
index 0000000000..84d52bb1cf
--- /dev/null
+++ b/chef/spec/data/compile/nodes/compile.rb
@@ -0,0 +1,5 @@
+##
+# Nodes should have a unique name
+##
+name "compile"
+recipes "test", "test::one", "test::two"
diff --git a/chef/spec/data/config.rb b/chef/spec/data/config.rb
new file mode 100644
index 0000000000..0b3340ce57
--- /dev/null
+++ b/chef/spec/data/config.rb
@@ -0,0 +1,6 @@
+#
+# Sample Chef Config File
+#
+
+cookbook_path "/etc/chef/cookbook", "/etc/chef/site-cookbook"
+
diff --git a/chef/spec/data/cookbooks/openldap/attributes/default.rb b/chef/spec/data/cookbooks/openldap/attributes/default.rb
new file mode 100644
index 0000000000..204ae9ed77
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/attributes/default.rb
@@ -0,0 +1,15 @@
+chef_env ||= nil
+case chef_env
+when "prod"
+ ldap_server "ops1prod"
+ ldap_basedn "dc=hjksolutions,dc=com"
+ ldap_replication_password "yes"
+when "corp"
+ ldap_server "ops1prod"
+ ldap_basedn "dc=hjksolutions,dc=com"
+ ldap_replication_password "yougotit"
+else
+ ldap_server "ops1prod"
+ ldap_basedn "dc=hjksolutions,dc=com"
+ ldap_replication_password "forsure"
+end
diff --git a/chef/spec/data/cookbooks/openldap/attributes/smokey.rb b/chef/spec/data/cookbooks/openldap/attributes/smokey.rb
new file mode 100644
index 0000000000..63f5b56c7f
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/attributes/smokey.rb
@@ -0,0 +1 @@
+smokey "robinson" \ No newline at end of file
diff --git a/chef/spec/data/cookbooks/openldap/definitions/client.rb b/chef/spec/data/cookbooks/openldap/definitions/client.rb
new file mode 100644
index 0000000000..ac81831d11
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/definitions/client.rb
@@ -0,0 +1,5 @@
+define :openldap_client, :mothra => "a big monster" do
+ cat "#{params[:name]}" do
+ pretty_kitty true
+ end
+end
diff --git a/chef/spec/data/cookbooks/openldap/definitions/server.rb b/chef/spec/data/cookbooks/openldap/definitions/server.rb
new file mode 100644
index 0000000000..2df437aa84
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/definitions/server.rb
@@ -0,0 +1,5 @@
+define :openldap_server, :mothra => "a big monster" do
+ cat "#{params[:name]}" do
+ pretty_kitty true
+ end
+end
diff --git a/chef/spec/data/cookbooks/openldap/ignore b/chef/spec/data/cookbooks/openldap/ignore
new file mode 100644
index 0000000000..e96f4e7df4
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/ignore
@@ -0,0 +1,6 @@
+#
+# The ignore file allows you to skip files in cookbooks with the same name that appear
+# later in the search path.
+#
+
+recipes/ignoreme.rb \ No newline at end of file
diff --git a/chef/spec/data/cookbooks/openldap/recipes/default.rb b/chef/spec/data/cookbooks/openldap/recipes/default.rb
new file mode 100644
index 0000000000..0ac8a9bb4b
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/recipes/default.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+ pretty_kitty true
+end
diff --git a/chef/spec/data/cookbooks/openldap/recipes/gigantor.rb b/chef/spec/data/cookbooks/openldap/recipes/gigantor.rb
new file mode 100644
index 0000000000..b450eca7cd
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/recipes/gigantor.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+ pretty_kitty false
+end
diff --git a/chef/spec/data/cookbooks/openldap/recipes/one.rb b/chef/spec/data/cookbooks/openldap/recipes/one.rb
new file mode 100644
index 0000000000..e1c3cff92e
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/recipes/one.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com default"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
diff --git a/chef/spec/data/cookbooks/openldap/templates/default/test.erb b/chef/spec/data/cookbooks/openldap/templates/default/test.erb
new file mode 100644
index 0000000000..f39fa7da89
--- /dev/null
+++ b/chef/spec/data/cookbooks/openldap/templates/default/test.erb
@@ -0,0 +1 @@
+We could be diving for pearls!
diff --git a/chef/spec/data/definitions/test.rb b/chef/spec/data/definitions/test.rb
new file mode 100644
index 0000000000..b0d0effc27
--- /dev/null
+++ b/chef/spec/data/definitions/test.rb
@@ -0,0 +1,5 @@
+define :rico_suave, :rich => "smooth" do
+ zen_master "test" do
+ something "#{params[:rich]}"
+ end
+end \ No newline at end of file
diff --git a/chef/spec/data/kitchen/openldap/attributes/default.rb b/chef/spec/data/kitchen/openldap/attributes/default.rb
new file mode 100644
index 0000000000..d208959475
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/attributes/default.rb
@@ -0,0 +1,3 @@
+#
+# Nothing to see here, move along
+#
diff --git a/chef/spec/data/kitchen/openldap/attributes/robinson.rb b/chef/spec/data/kitchen/openldap/attributes/robinson.rb
new file mode 100644
index 0000000000..9d6b44d464
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/attributes/robinson.rb
@@ -0,0 +1,3 @@
+#
+# Smokey lives here
+# \ No newline at end of file
diff --git a/chef/spec/data/kitchen/openldap/definitions/client.rb b/chef/spec/data/kitchen/openldap/definitions/client.rb
new file mode 100644
index 0000000000..d4c2263b54
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/definitions/client.rb
@@ -0,0 +1,3 @@
+#
+# A sad client
+#
diff --git a/chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb b/chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb
new file mode 100644
index 0000000000..510f0c35da
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/definitions/drewbarrymore.rb
@@ -0,0 +1,3 @@
+#
+# Was in people magazine this month...
+# \ No newline at end of file
diff --git a/chef/spec/data/kitchen/openldap/recipes/gigantor.rb b/chef/spec/data/kitchen/openldap/recipes/gigantor.rb
new file mode 100644
index 0000000000..70a41960eb
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/recipes/gigantor.rb
@@ -0,0 +1,3 @@
+cat "blanket" do
+ pretty_kitty true
+end \ No newline at end of file
diff --git a/chef/spec/data/kitchen/openldap/recipes/ignoreme.rb b/chef/spec/data/kitchen/openldap/recipes/ignoreme.rb
new file mode 100644
index 0000000000..15095986c6
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/recipes/ignoreme.rb
@@ -0,0 +1,3 @@
+#
+# this file will never be seen
+# \ No newline at end of file
diff --git a/chef/spec/data/kitchen/openldap/recipes/woot.rb b/chef/spec/data/kitchen/openldap/recipes/woot.rb
new file mode 100644
index 0000000000..44893dae36
--- /dev/null
+++ b/chef/spec/data/kitchen/openldap/recipes/woot.rb
@@ -0,0 +1,3 @@
+#
+# Such a funny word..
+#
diff --git a/chef/spec/data/nodes/default.rb b/chef/spec/data/nodes/default.rb
new file mode 100644
index 0000000000..e1c3cff92e
--- /dev/null
+++ b/chef/spec/data/nodes/default.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com default"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
diff --git a/chef/spec/data/nodes/test.example.com.rb b/chef/spec/data/nodes/test.example.com.rb
new file mode 100644
index 0000000000..9c374395bf
--- /dev/null
+++ b/chef/spec/data/nodes/test.example.com.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
diff --git a/chef/spec/data/nodes/test.rb b/chef/spec/data/nodes/test.rb
new file mode 100644
index 0000000000..d968816d60
--- /dev/null
+++ b/chef/spec/data/nodes/test.rb
@@ -0,0 +1,15 @@
+##
+# Nodes should have a unique name
+##
+name "test.example.com short"
+
+##
+# Nodes can set arbitrary arguments
+##
+sunshine "in"
+something "else"
+
+##
+# Nodes should have recipes
+##
+recipes "operations-master", "operations-monitoring"
diff --git a/chef/spec/data/recipes/test.rb b/chef/spec/data/recipes/test.rb
new file mode 100644
index 0000000000..1c94b917f0
--- /dev/null
+++ b/chef/spec/data/recipes/test.rb
@@ -0,0 +1,7 @@
+
+file "/etc/nsswitch.conf" do
+ action "create"
+ owner "root"
+ group "root"
+ mode 0644
+end
diff --git a/chef/spec/data/seattle.txt b/chef/spec/data/seattle.txt
new file mode 100644
index 0000000000..19f6290939
--- /dev/null
+++ b/chef/spec/data/seattle.txt
@@ -0,0 +1 @@
+Seattle is a great town. Next time you visit, you should buy me a beer. \ No newline at end of file
diff --git a/chef/spec/lib/chef/provider/easy.rb b/chef/spec/lib/chef/provider/easy.rb
new file mode 100644
index 0000000000..b637efa768
--- /dev/null
+++ b/chef/spec/lib/chef/provider/easy.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+class Chef
+ class Provider
+ class Easy < Chef::Provider
+ def load_current_resource
+ true
+ end
+
+ def action_sell
+ true
+ end
+
+ def action_buy
+ true
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/spec/lib/chef/provider/snakeoil.rb b/chef/spec/lib/chef/provider/snakeoil.rb
new file mode 100644
index 0000000000..0486402686
--- /dev/null
+++ b/chef/spec/lib/chef/provider/snakeoil.rb
@@ -0,0 +1,37 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+class Chef
+ class Provider
+ class SnakeOil < Chef::Provider
+ def load_current_resource
+ true
+ end
+
+ def action_sell
+ true
+ end
+
+ def action_buy
+ true
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/spec/lib/chef/resource/cat.rb b/chef/spec/lib/chef/resource/cat.rb
new file mode 100644
index 0000000000..e651c44512
--- /dev/null
+++ b/chef/spec/lib/chef/resource/cat.rb
@@ -0,0 +1,42 @@
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+class Chef
+ class Resource
+ class Cat < Chef::Resource
+
+ attr_accessor :action
+
+ def initialize(name, collection=nil, node=nil)
+ @resource_name = :cat
+ super(name, collection, node)
+ @action = "sell"
+ end
+
+ def pretty_kitty(arg=nil)
+ set_if_args(@pretty_kitty, arg) do
+ case arg
+ when true, false
+ @pretty_kitty = arg
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/spec/lib/chef/resource/zen_master.rb b/chef/spec/lib/chef/resource/zen_master.rb
new file mode 100644
index 0000000000..8b4225fbd4
--- /dev/null
+++ b/chef/spec/lib/chef/resource/zen_master.rb
@@ -0,0 +1,44 @@
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+class Chef
+ class Resource
+ class ZenMaster < Chef::Resource
+ attr_reader :peace
+
+ def initialize(name, collection=nil, node=nil)
+ @resource_name = :zen_master
+ super(name, collection, node)
+ end
+
+ def peace(tf)
+ @peace = tf
+ end
+
+ def something(arg=nil)
+ set_if_args(@something, arg) do
+ case arg
+ when true, false
+ @something = arg
+ end
+ end
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/spec/rcov.opts b/chef/spec/rcov.opts
new file mode 100644
index 0000000000..484626ea9c
--- /dev/null
+++ b/chef/spec/rcov.opts
@@ -0,0 +1,2 @@
+--exclude
+spec,bin,/Library/Ruby
diff --git a/chef/spec/spec.opts b/chef/spec/spec.opts
new file mode 100644
index 0000000000..c9c9b4ddf0
--- /dev/null
+++ b/chef/spec/spec.opts
@@ -0,0 +1,3 @@
+--color
+--loadby
+mtime
diff --git a/chef/spec/spec_helper.rb b/chef/spec/spec_helper.rb
new file mode 100644
index 0000000000..9120b2876c
--- /dev/null
+++ b/chef/spec/spec_helper.rb
@@ -0,0 +1,23 @@
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: GNU General Public License version 2 or later
+#
+# This program and entire repository is free software; you can
+# redistribute it and/or modify it under the terms of the GNU
+# General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+
+require File.join(File.dirname(__FILE__), "..", "lib", "chef")
+Dir[File.join(File.dirname(__FILE__), 'lib', '**', '*.rb')].sort.each { |lib| require lib }
+
+Chef::Config.log_level(:error)
diff --git a/chef/spec/unit/chef_spec.rb b/chef/spec/unit/chef_spec.rb
new file mode 100644
index 0000000000..7d2af6f88d
--- /dev/null
+++ b/chef/spec/unit/chef_spec.rb
@@ -0,0 +1,25 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef do
+ it "should have a version defined" do
+ Chef::VERSION.should match(/(\d+)\.(\d+)\.(\d+)/)
+ end
+end
diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb
new file mode 100644
index 0000000000..a2732f28c3
--- /dev/null
+++ b/chef/spec/unit/client_spec.rb
@@ -0,0 +1,61 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Client, "initialize" do
+ it "should create a new Chef::Client object" do
+ Chef::Client.new.should be_kind_of(Chef::Client)
+ end
+end
+
+describe Chef::Client, "build_node" do
+ before(:each) do
+ @mock_facter_fqdn = mock("Facter FQDN")
+ @mock_facter_fqdn.stub!(:value).and_return("foo.bar.com")
+ @mock_facter_hostname = mock("Facter Hostname")
+ @mock_facter_hostname.stub!(:value).and_return("foo")
+ Facter.stub!(:[]).with("fqdn").and_return(@mock_facter_fqdn)
+ Facter.stub!(:[]).with("hostname").and_return(@mock_facter_hostname)
+ Facter.stub!(:each).and_return(true)
+ @client = Chef::Client.new
+ end
+
+ it "should set the name equal to the FQDN" do
+ @client.build_node
+ @client.node.name.should eql("foo.bar.com")
+ end
+
+ it "should set the name equal to the hostname if FQDN is not available" do
+ @mock_facter_fqdn.stub!(:value).and_return(nil)
+ @client.build_node
+ @client.node.name.should eql("foo")
+ end
+end
+
+describe Chef::Client, "register" do
+ before(:each) do
+ @client = Chef::Client.new
+ end
+
+ it "should check to see if it's already registered"
+
+ it "should create a new passphrase if not registered"
+
+ it "should create a new registration if it has not registered"
+end \ No newline at end of file
diff --git a/chef/spec/unit/compile_spec.rb b/chef/spec/unit/compile_spec.rb
new file mode 100644
index 0000000000..c7f76a4a58
--- /dev/null
+++ b/chef/spec/unit/compile_spec.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Compile do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "compile", "nodes"))
+ Chef::Config.cookbook_path(File.join(File.dirname(__FILE__), "..", "data", "compile", "cookbooks"))
+ @compile = Chef::Compile.new
+ end
+
+ it "should create a new Chef::Compile" do
+ @compile.should be_a_kind_of(Chef::Compile)
+ end
+
+ it "should have a Chef::CookbookLoader" do
+ @compile.cookbook_loader.should be_a_kind_of(Chef::CookbookLoader)
+ end
+
+ it "should have a Chef::ResourceCollection" do
+ @compile.collection.should be_a_kind_of(Chef::ResourceCollection)
+ end
+
+ it "should have a hash of Definitions" do
+ @compile.definitions.should be_a_kind_of(Hash)
+ end
+
+ it "should load a node by name" do
+ node = Chef::Node.new
+ Chef::Node.stub!(:load).and_return(node)
+ lambda {
+ @compile.load_node("compile")
+ }.should_not raise_error
+ @compile.node.name.should == "compile"
+ end
+
+ it "should load all the definitions" do
+ lambda { @compile.load_definitions }.should_not raise_error
+ @compile.definitions.should have_key(:new_cat)
+ end
+
+ it "should load all the recipes specified for this node" do
+ node = Chef::Node.new
+ Chef::Node.stub!(:load).and_return(node)
+ @compile.load_node("compile")
+ @compile.load_definitions
+ lambda { @compile.load_recipes }.should_not raise_error
+ @compile.collection[0].to_s.should == "cat[einstein]"
+ @compile.collection[1].to_s.should == "cat[loulou]"
+ @compile.collection[2].to_s.should == "cat[birthday]"
+ @compile.collection[3].to_s.should == "cat[peanut]"
+ @compile.collection[4].to_s.should == "cat[fat peanut]"
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/config_spec.rb b/chef/spec/unit/config_spec.rb
new file mode 100644
index 0000000000..51e33401d8
--- /dev/null
+++ b/chef/spec/unit/config_spec.rb
@@ -0,0 +1,91 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Config do
+
+ it "should load a .rb file in context" do
+ lambda {
+ Chef::Config.from_file(File.join(File.dirname(__FILE__), "..", "data", "config.rb"))
+ }.should_not raise_error
+ end
+
+ it "should raise an ArgumentError with an explanation if you try and set a non-existent variable" do
+ lambda {
+ Chef::Config.from_file(File.join(File.dirname(__FILE__), "..", "data", "bad-config.rb"))
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an IOError if it can't find the file" do
+ lambda {
+ Chef::Config.from_file("/tmp/timmytimmytimmy")
+ }.should raise_error(IOError)
+ end
+
+ it "should have a default cookbook_path" do
+ Chef::Config.cookbook_path.should be_kind_of(Array)
+ end
+
+ it "should allow you to set a cookbook_path with a string" do
+ Chef::Config.cookbook_path("/etc/chef/cookbook")
+ Chef::Config.cookbook_path.should eql("/etc/chef/cookbook")
+ end
+
+ it "should allow you to set a cookbook_path with multiple strings" do
+ Chef::Config.cookbook_path("/etc/chef/cookbook", "/etc/chef/upstream-cookbooks")
+ Chef::Config.cookbook_path.should eql([
+ "/etc/chef/cookbook",
+ "/etc/chef/upstream-cookbooks"
+ ])
+ end
+
+ it "should allow you to set a cookbook_path with an array" do
+ Chef::Config.cookbook_path ["one", "two"]
+ Chef::Config.cookbook_path.should eql(["one", "two"])
+ end
+
+ it "should allow you to reference a value by index" do
+ Chef::Config[:cookbook_path].should be_kind_of(Array)
+ end
+
+ it "should allow you to set a value by index" do
+ Chef::Config[:cookbook_path] = "one"
+ Chef::Config[:cookbook_path].should == "one"
+ end
+
+ it "should allow you to set config values with a block" do
+ Chef::Config.configure do |c|
+ c[:cookbook_path] = "monkey_rabbit"
+ c[:otherthing] = "boo"
+ end
+ Chef::Config.cookbook_path.should == "monkey_rabbit"
+ Chef::Config.otherthing.should == "boo"
+ end
+
+ it "should raise an ArgumentError if you access a config option that does not exist" do
+ lambda { Chef::Config[:snob_hobbery] }.should raise_error(ArgumentError)
+ end
+
+ it "should return true or false with has_key?" do
+ Chef::Config.has_key?(:monkey).should eql(false)
+ Chef::Config[:monkey] = "gotcha"
+ Chef::Config.has_key?(:monkey).should eql(true)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/cookbook_loader_spec.rb b/chef/spec/unit/cookbook_loader_spec.rb
new file mode 100644
index 0000000000..501329777b
--- /dev/null
+++ b/chef/spec/unit/cookbook_loader_spec.rb
@@ -0,0 +1,117 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::CookbookLoader do
+ before(:each) do
+ Chef::Config.cookbook_path [
+ File.join(File.dirname(__FILE__), "..", "data", "cookbooks"),
+ File.join(File.dirname(__FILE__), "..", "data", "kitchen")
+ ]
+ @cl = Chef::CookbookLoader.new()
+ end
+
+ it "should be a Chef::CookbookLoader object" do
+ @cl.should be_kind_of(Chef::CookbookLoader)
+ end
+
+ it "should return cookbook objects with []" do
+ @cl[:openldap].should be_a_kind_of(Chef::Cookbook)
+ end
+
+ it "should raise an exception if it cannot find a cookbook with []" do
+ lambda { @cl[:monkeypoop] }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to look up available cookbooks with [] and a symbol" do
+ @cl[:openldap].name.should eql(:openldap)
+ end
+
+ it "should allow you to look up available cookbooks with [] and a string" do
+ @cl["openldap"].name.should eql(:openldap)
+ end
+
+ it "should allow you to iterate over cookbooks with each" do
+ seen = Hash.new
+ @cl.each do |cb|
+ seen[cb.name] = true
+ end
+ seen.should have_key(:openldap)
+ seen.should have_key(:apache2)
+ end
+
+ it "should find all the cookbooks in the cookbook path" do
+ Chef::Config.cookbook_path << File.join(File.dirname(__FILE__), "..", "data", "hidden-cookbooks")
+ @cl.load_cookbooks
+ @cl.detect { |cb| cb.name == :openldap }.should_not eql(nil)
+ @cl.detect { |cb| cb.name == :apache2 }.should_not eql(nil)
+ end
+
+ it "should allow you to override an attribute file via cookbook_path" do
+ @cl[:openldap].attribute_files.detect { |f|
+ f =~ /cookbooks\/openldap\/attributes\/default.rb/
+ }.should_not eql(nil)
+ @cl[:openldap].attribute_files.detect { |f|
+ f =~ /kitchen\/openldap\/attributes\/default.rb/
+ }.should eql(nil)
+ end
+
+ it "should load different attribute files from deeper paths" do
+ @cl[:openldap].attribute_files.detect { |f|
+ f =~ /kitchen\/openldap\/attributes\/robinson.rb/
+ }.should_not eql(nil)
+ end
+
+ it "should allow you to override a definition file via cookbook_path" do
+ @cl[:openldap].definition_files.detect { |f|
+ f =~ /cookbooks\/openldap\/definitions\/client.rb/
+ }.should_not eql(nil)
+ @cl[:openldap].definition_files.detect { |f|
+ f =~ /kitchen\/openldap\/definitions\/client.rb/
+ }.should eql(nil)
+ end
+
+ it "should load definition files from deeper paths" do
+ @cl[:openldap].definition_files.detect { |f|
+ f =~ /kitchen\/openldap\/definitions\/drewbarrymore.rb/
+ }.should_not eql(nil)
+ end
+
+ it "should allow you to override a recipe file via cookbook_path" do
+ @cl[:openldap].recipe_files.detect { |f|
+ f =~ /cookbooks\/openldap\/recipes\/gigantor.rb/
+ }.should_not eql(nil)
+ @cl[:openldap].recipe_files.detect { |f|
+ f =~ /kitchen\/openldap\/recipes\/gigantor.rb/
+ }.should eql(nil)
+ end
+
+ it "should load recipe files from deeper paths" do
+ @cl[:openldap].recipe_files.detect { |f|
+ f =~ /kitchen\/openldap\/recipes\/woot.rb/
+ }.should_not eql(nil)
+ end
+
+ it "should allow you to have an 'ignore' file, which skips loading files in later cookbooks" do
+ @cl[:openldap].recipe_files.detect { |f|
+ f =~ /kitchen\/openldap\/recipes\/ignoreme.rb/
+ }.should eql(nil)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/cookbook_spec.rb b/chef/spec/unit/cookbook_spec.rb
new file mode 100644
index 0000000000..85b4040771
--- /dev/null
+++ b/chef/spec/unit/cookbook_spec.rb
@@ -0,0 +1,142 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Cookbook do
+ COOKBOOK_PATH = File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap")
+
+ before(:each) do
+ @cookbook = Chef::Cookbook.new("openldap")
+ end
+
+ it "should be a Chef::Cookbook object" do
+ @cookbook.should be_kind_of(Chef::Cookbook)
+ end
+
+ it "should have a name" do
+ @cookbook.name.should eql("openldap")
+ end
+
+ it "should have a list of attribute files" do
+ @cookbook.attribute_files.should be_kind_of(Array)
+ end
+
+ it "should allow you to set the list of attribute files" do
+ @cookbook.attribute_files = [ "one", "two" ]
+ @cookbook.attribute_files.should eql(["one", "two"])
+ end
+
+ it "should allow you to load all the attributes" do
+ node = Chef::Node.new
+ node.name "Julia Child"
+ node.chef_env false
+ @cookbook.attribute_files = Dir[File.join(COOKBOOK_PATH, "attributes", "**", "*.rb")]
+ node = @cookbook.load_attributes(node)
+ node.ldap_server.should eql("ops1prod")
+ node.ldap_basedn.should eql("dc=hjksolutions,dc=com")
+ node.ldap_replication_password.should eql("forsure")
+ node.smokey.should eql("robinson")
+ end
+
+ it "should raise an ArgumentError if you don't pass a node object to load_attributes" do
+ lambda { @cookbook.load_attributes("snake oil") }.should raise_error(ArgumentError)
+ end
+
+ it "should have a list of definition files" do
+ @cookbook.definition_files.should be_a_kind_of(Array)
+ end
+
+ it "should allow you to set the list of definition files" do
+ @cookbook.definition_files = [ "one", "two" ]
+ @cookbook.definition_files.should eql(["one", "two"])
+ end
+
+ it "should allow you to load all the definitions, returning a hash of ResourceDefinitions by name" do
+ @cookbook.definition_files = Dir[File.join(COOKBOOK_PATH, "definitions", "**", "*.rb")]
+ defs = @cookbook.load_definitions
+ defs.has_key?(:openldap_server).should eql(true)
+ defs[:openldap_server].should be_a_kind_of(Chef::ResourceDefinition)
+ defs.has_key?(:openldap_client).should eql(true)
+ defs[:openldap_client].should be_a_kind_of(Chef::ResourceDefinition)
+ end
+
+ it "should have a list of recipe files" do
+ @cookbook.recipe_files.should be_a_kind_of(Array)
+ end
+
+ it "should allow you to set the list of recipe files" do
+ @cookbook.recipe_files = [ "one", "two" ]
+ @cookbook.recipe_files.should eql(["one", "two"])
+ end
+
+ it "should have a list of recipes by name" do
+ @cookbook.recipe_files = [ "one", "two" ]
+ @cookbook.recipes.detect { |r| r == "openldap::one" }.should eql("openldap::one")
+ @cookbook.recipes.detect { |r| r == "openldap::two" }.should eql("openldap::two")
+ end
+
+ it "should take a file /path.rb, and use the filename minus rb as a recipe name" do
+ @cookbook.recipe_files = [ "/something/one.rb", "/otherthing/two.rb" ]
+ @cookbook.recipes.detect { |r| r == "openldap::one" }.should eql("openldap::one")
+ @cookbook.recipes.detect { |r| r == "openldap::two" }.should eql("openldap::two")
+ end
+
+ it "should take a file path.rb, and use the filename minus rb as a recipe name" do
+ @cookbook.recipe_files = [ "one.rb", "two.rb" ]
+ @cookbook.recipes.detect { |r| r == "openldap::one" }.should eql("openldap::one")
+ @cookbook.recipes.detect { |r| r == "openldap::two" }.should eql("openldap::two")
+ end
+
+ it "should allow you to test for a recipe with recipe?" do
+ @cookbook.recipe_files = [ "one", "two" ]
+ @cookbook.recipe?("one").should eql(true)
+ @cookbook.recipe?("shanghai").should eql(false)
+ end
+
+ it "should allow you to test for a recipe? with a fq recipe name" do
+ @cookbook.recipe_files = [ "one", "two" ]
+ @cookbook.recipe?("openldap::one").should eql(true)
+ @cookbook.recipe?("shanghai::city").should eql(false)
+ end
+
+ it "should allow you to run a recipe by name via load_recipe" do
+ @cookbook.recipe_files = Dir[File.join(COOKBOOK_PATH, "recipes", "**", "*.rb")]
+ node = Chef::Node.new
+ node.name "Julia Child"
+ recipe = @cookbook.load_recipe("openldap::gigantor", node)
+ recipe.recipe_name.should eql("gigantor")
+ recipe.cookbook_name.should eql("openldap")
+ recipe.collection[0].name.should eql("blanket")
+ end
+
+ it "should raise an ArgumentException if you try to load a bad recipe name" do
+ node = Chef::Node.new
+ node.name "Julia Child"
+ lambda { @cookbook.load_recipe("smackdown", node) }.should raise_error(ArgumentError)
+ end
+
+ it "should load the attributes if it has not already when a recipe is loaded" do
+ @cookbook.attribute_files = Dir[File.join(COOKBOOK_PATH, "attributes", "smokey.rb")]
+ @cookbook.recipe_files = Dir[File.join(COOKBOOK_PATH, "recipes", "**", "*.rb")]
+ node = Chef::Node.new
+ node.name "Julia Child"
+ recipe = @cookbook.load_recipe("openldap::gigantor", node)
+ node.smokey.should == "robinson"
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/couchdb_spec.rb b/chef/spec/unit/couchdb_spec.rb
new file mode 100644
index 0000000000..621439df51
--- /dev/null
+++ b/chef/spec/unit/couchdb_spec.rb
@@ -0,0 +1,212 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::CouchDB, "new" do
+ it "should create a new Chef::REST object from the default url" do
+ Chef::Config[:couchdb_url] = "http://monkey"
+ Chef::REST.should_receive(:new).with("http://monkey").and_return(true)
+ Chef::CouchDB.new
+ end
+
+ it "should create a new Chef::REST object from a provided url" do
+ Chef::REST.should_receive(:new).with("http://monkeypants").and_return(true)
+ Chef::CouchDB.new("http://monkeypants")
+ end
+end
+
+describe Chef::CouchDB, "create_db" do
+ before(:each) do
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ @mock_rest.stub!(:get_rest).and_return([ "chef" ])
+ @mock_rest.stub!(:put_rest).and_return(true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ end
+
+ def do_create_db
+ couch = Chef::CouchDB.new
+ couch.create_db
+ end
+
+ it "should get a list of current databases" do
+ @mock_rest.should_receive(:get_rest).and_return(["chef"])
+ do_create_db
+ end
+
+ it "should create the chef database if it does not exist" do
+ @mock_rest.stub!(:get_rest).and_return([])
+ @mock_rest.should_receive(:put_rest).with("chef", {}).and_return(true)
+ do_create_db
+ end
+
+ it "should not create the chef database if it does exist" do
+ @mock_rest.stub!(:get_rest).and_return(["chef"])
+ @mock_rest.should_not_receive(:put_rest)
+ do_create_db
+ end
+
+ it "should return 'chef'" do
+ do_create_db.should eql("chef")
+ end
+end
+
+describe Chef::CouchDB, "create_design_document" do
+ before(:each) do
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ @mock_design = {
+ "version" => 1,
+ "_rev" => 1
+ }
+ @mock_data = {
+ "version" => 1,
+ "language" => "javascript",
+ "views" => {
+ "all" => {
+ "map" => <<-EOJS
+ function(doc) {
+ if (doc.chef_type == "node") {
+ emit(doc.name, doc);
+ }
+ }
+ EOJS
+ },
+ }
+ }
+ @mock_rest.stub!(:get_rest).and_return(@mock_design)
+ @mock_rest.stub!(:put_rest).and_return(true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ end
+
+ def do_create_design_document
+ couchdb = Chef::CouchDB.new
+ couchdb.create_design_document("bob", @mock_data)
+ end
+
+ it "should fetch the existing design document" do
+ @mock_rest.should_receive(:get_rest).with("chef/_design%2Fbob")
+ do_create_design_document
+ end
+
+ it "should populate the _rev in the new design if the versions dont match" do
+ @mock_data["version"] = 2
+ do_create_design_document
+ @mock_data["_rev"].should eql(1)
+ end
+
+ it "should create the view if it requires updating" do
+ @mock_data["version"] = 2
+ @mock_rest.should_receive(:put_rest).with("chef/_design%2Fbob", @mock_data)
+ do_create_design_document
+ end
+
+ it "should not create the view if it does not require updating" do
+ @mock_data["version"] = 1
+ @mock_rest.should_not_receive(:put_rest)
+ do_create_design_document
+ end
+end
+
+describe Chef::CouchDB, "store" do
+ it "should put the object into couchdb" do
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ @mock_rest.should_receive(:put_rest).with("chef/node_bob", {}).and_return(true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ Chef::CouchDB.new.store("node", "bob", {})
+ end
+end
+
+describe Chef::CouchDB, "load" do
+ it "should load the object from couchdb" do
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ @mock_rest.should_receive(:get_rest).with("chef/node_bob").and_return(true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ Chef::CouchDB.new.load("node", "bob").should eql(true)
+ end
+end
+
+describe Chef::CouchDB, "delete" do
+ before(:each) do
+ @mock_current = {
+ "version" => 1,
+ "_rev" => 1
+ }
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ @mock_rest.stub!(:get_rest).and_return(@mock_current)
+ @mock_rest.stub!(:delete_rest).and_return(true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ end
+
+ def do_delete(rev=nil)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ Chef::CouchDB.new.delete("node", "bob", rev)
+ end
+
+ it "should remove the object from couchdb with a specific revision" do
+ @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=1")
+ do_delete(1)
+ end
+
+ it "should remove the object from couchdb based on the couchdb_rev of the current obj" do
+ mock_real = mock("Inflated Object")
+ mock_real.stub!(:respond_to?).and_return(true)
+ mock_real.stub!(:couchdb_rev).and_return(2)
+ @mock_rest.should_receive(:get_rest).with("chef/node_bob").and_return(mock_real)
+ @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=2")
+ do_delete
+ end
+
+ it "should remove the object from couchdb based on the current objects rev" do
+ @mock_rest.should_receive(:delete_rest).with("chef/node_bob?rev=1")
+ do_delete
+ end
+end
+
+describe Chef::CouchDB, "list" do
+ before(:each) do
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ end
+
+ it "should get the view for all objects if inflate is true" do
+ @mock_rest.should_receive(:get_rest).with("chef/_view/node/all").and_return(true)
+ Chef::CouchDB.new.list("node", true)
+ end
+
+ it "should get the view for just the object id's if inflate is false" do
+ @mock_rest.should_receive(:get_rest).with("chef/_view/node/all_id").and_return(true)
+ Chef::CouchDB.new.list("node", false)
+ end
+end
+
+describe Chef::CouchDB, "has_key?" do
+ before(:each) do
+ @mock_rest = mock("Chef::REST", :null_object => true)
+ Chef::REST.stub!(:new).and_return(@mock_rest)
+ end
+
+ it "should return true if the object exists" do
+ @mock_rest.should_receive(:get_rest).and_return(true)
+ Chef::CouchDB.new.has_key?("node", "bob").should eql(true)
+ end
+
+ it "should return false if the object does not exist" do
+ @mock_rest.should_receive(:get_rest).and_raise(ArgumentError)
+ Chef::CouchDB.new.has_key?("node", "bob").should eql(false)
+ end
+end
diff --git a/chef/spec/unit/file_cache_spec.rb b/chef/spec/unit/file_cache_spec.rb
new file mode 100644
index 0000000000..c84eea376e
--- /dev/null
+++ b/chef/spec/unit/file_cache_spec.rb
@@ -0,0 +1,124 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::FileCache, "store method" do
+ before(:each) do
+ Chef::Config[:file_cache_path] = "/tmp/foo"
+ Dir.stub!(:mkdir).and_return(true)
+ File.stub!(:directory?).and_return(true)
+ @io = mock("IO", { :print => true, :close => true })
+ File.stub!(:open).and_return(@io)
+ end
+
+ it "should create the directories leading up to bang" do
+ File.stub!(:directory?).and_return(false)
+ Dir.should_receive(:mkdir).with("/tmp").and_return(true)
+ Dir.should_receive(:mkdir).with("/tmp/foo").and_return(true)
+ Dir.should_receive(:mkdir).with("/tmp/foo/whiz").and_return(true)
+ Dir.should_not_receive(:mkdir).with("/tmp/foo/whiz/bang").and_return(true)
+ Chef::FileCache.store("whiz/bang", "I found a poop")
+ end
+
+ it "should create a file at /tmp/foo/whiz/bang" do
+ File.should_receive(:open).with("/tmp/foo/whiz/bang", "w").and_return(@io)
+ Chef::FileCache.store("whiz/bang", "I found a poop")
+ end
+
+ it "should print the contents to the file" do
+ @io.should_receive(:print).with("I found a poop")
+ Chef::FileCache.store("whiz/bang", "I found a poop")
+ end
+
+ it "should close the file" do
+ @io.should_receive(:close)
+ Chef::FileCache.store("whiz/bang", "I found a poop")
+ end
+
+end
+
+describe Chef::FileCache, "load method" do
+ before(:each) do
+ Chef::Config[:file_cache_path] = "/tmp/foo"
+ Dir.stub!(:mkdir).and_return(true)
+ File.stub!(:directory?).and_return(true)
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:read).and_return("I found a poop")
+ end
+
+ it "should find the full path to whiz/bang" do
+ File.should_receive(:read).with("/tmp/foo/whiz/bang").and_return(true)
+ Chef::FileCache.load('whiz/bang')
+ end
+
+ it "should raise a Chef::Exception::FileNotFound if the file doesn't exist" do
+ File.stub!(:exists?).and_return(false)
+ lambda { Chef::FileCache.load('whiz/bang') }.should raise_error(Chef::Exception::FileNotFound)
+ end
+end
+
+describe Chef::FileCache, "delete method" do
+ before(:each) do
+ Chef::Config[:file_cache_path] = "/tmp/foo"
+ Dir.stub!(:mkdir).and_return(true)
+ File.stub!(:directory?).and_return(true)
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:unlink).and_return(true)
+ end
+
+ it "should unlink the full path to whiz/bang" do
+ File.should_receive(:unlink).with("/tmp/foo/whiz/bang").and_return(true)
+ Chef::FileCache.delete("whiz/bang")
+ end
+
+end
+
+describe Chef::FileCache, "list method" do
+ before(:each) do
+ Chef::Config[:file_cache_path] = "/tmp/foo"
+ Dir.stub!(:[]).and_return(["/tmp/foo/whiz/bang", "/tmp/foo/snappy/patter"])
+ File.stub!(:file?).and_return(true)
+ end
+
+ it "should return the relative paths" do
+ Chef::FileCache.list.should eql([ "whiz/bang", "snappy/patter" ])
+ end
+end
+
+describe Chef::FileCache, "has_key? method" do
+ before(:each) do
+ Chef::Config[:file_cache_path] = "/tmp/foo"
+ end
+
+ it "should check the full path to the file" do
+ File.should_receive(:exists?).with("/tmp/foo/whiz/bang")
+ Chef::FileCache.has_key?("whiz/bang")
+ end
+
+ it "should return true if the file exists" do
+ File.stub!(:exists?).and_return(true)
+ Chef::FileCache.has_key?("whiz/bang").should eql(true)
+ end
+
+ it "should return false if the file does not exist" do
+ File.stub!(:exists?).and_return(false)
+ Chef::FileCache.has_key?("whiz/bang").should eql(false)
+ end
+end
+
diff --git a/chef/spec/unit/file_store_spec.rb b/chef/spec/unit/file_store_spec.rb
new file mode 100644
index 0000000000..8addeb9e31
--- /dev/null
+++ b/chef/spec/unit/file_store_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+class Fakestore
+ attr_accessor :name
+
+ def to_json(*a)
+ { :name => @name }.to_json(*a)
+ end
+
+ def self.json_create(o)
+ new_fakestore = new
+ new_fakestore.name = o[:name]
+ new_fakestore
+ end
+end
+
+describe Chef::FileStore do
+ before(:each) do
+ Chef::Config[:file_store_path] = "/tmp/chef-test"
+ @fakestore = Fakestore.new
+ @fakestore.name = "Landslide"
+ @fakestore_digest = "a56a428bddac69e505731708ba206da0bb75e8de883bb4d5ef6be9b327da556a"
+ end
+
+ it "should return a path to a file given a type and key" do
+ Dir.stub!(:mkdir).and_return(true)
+ File.stub!(:directory?).and_return(true)
+ path = Chef::FileStore.create_store_path("fakestore", @fakestore.name)
+ path.should eql("/tmp/chef-test/fakestore/a/56a/Landslide")
+ end
+
+ it "should create directories for the path if needed" do
+ File.stub!(:directory?).and_return(false)
+ Dir.should_receive(:mkdir).exactly(4).times.and_return(true)
+ Chef::FileStore.create_store_path("fakestore", @fakestore.name)
+ end
+
+ it "should store an object with a type and key" do
+ Chef::FileStore.should_receive(:create_store_path).with("fakestore", @fakestore.name).and_return("/monkey")
+ File.stub!(:directory?).and_return(true)
+ ioobj = mock("IO", :null_object => true)
+ ioobj.should_receive(:puts).with(@fakestore.to_json)
+ ioobj.should_receive(:close).once.and_return(true)
+ File.should_receive(:open).with("/monkey", "w").and_return(ioobj)
+ Chef::FileStore.store("fakestore", @fakestore.name, @fakestore)
+ end
+
+ it "should load an object from the store with type and key" do
+ Chef::FileStore.should_receive(:create_store_path).with("fakestore", @fakestore.name).and_return("/monkey")
+ File.stub!(:exists?).and_return(true)
+ IO.should_receive(:read).once.and_return(true)
+ JSON.should_receive(:parse).and_return(true)
+ Chef::FileStore.load("fakestore", @fakestore.name)
+ end
+
+ it "should through an exception if it cannot load a file from the store" do
+ Chef::FileStore.should_receive(:create_store_path).and_return("/tmp")
+ File.stub!(:exists?).and_return(false)
+ lambda { Chef::FileStore.load("fakestore", @fakestore.name) }.should raise_error(RuntimeError)
+ end
+
+ it "should delete a file from the store if it exists" do
+ Chef::FileStore.should_receive(:create_store_path).with("node", "nothing").and_return("/tmp/foolio")
+ File.stub!(:exists?).and_return(true)
+ File.should_receive(:unlink).with("/tmp/foolio").and_return(1)
+ Chef::FileStore.delete("node", "nothing")
+ end
+
+ it "should list all the keys of a particular type" do
+ Dir.should_receive(:[]).with("/tmp/chef-test/node/**/*").and_return(["pool"])
+ File.should_receive(:file?).with("pool").and_return(true)
+ Chef::FileStore.list("node").should eql(["pool"])
+ end
+
+ it "should return all the documents of a particular type with list and inflate" do
+ Dir.stub!(:[]).and_return(["/foo/pool"])
+ File.stub!(:file?).and_return(true)
+ Chef::FileStore.should_receive(:load).with("node", "pool").and_return("monkey")
+ Chef::FileStore.list("node", true).should eql(["monkey"])
+ end
+
+ it "should let you test whether a key doesnt exist for an object type with has_key?" do
+ Dir.should_receive(:[]).with("/tmp/chef-test/node/**/*").and_return(["pool"])
+ File.should_receive(:file?).with("pool").and_return(true)
+ Chef::FileStore.has_key?("node", "snake").should eql(false)
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/log/formatter_spec.rb b/chef/spec/unit/log/formatter_spec.rb
new file mode 100644
index 0000000000..e0d8bfd773
--- /dev/null
+++ b/chef/spec/unit/log/formatter_spec.rb
@@ -0,0 +1,51 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'time'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Log::Formatter do
+ before(:each) do
+ @formatter = Chef::Log::Formatter.new
+ end
+
+ it "should print raw strings with msg2str(string)" do
+ @formatter.msg2str("nuthin new").should == "nuthin new"
+ end
+
+ it "should format exceptions properly with msg2str(e)" do
+ e = IOError.new("legendary roots crew")
+ @formatter.msg2str(e).should == "legendary roots crew (IOError)\n"
+ end
+
+ it "should format random objects via inspect with msg2str(Object)" do
+ @formatter.msg2str([ "black thought", "?uestlove" ]).should == '["black thought", "?uestlove"]'
+ end
+
+ it "should return a formatted string with call" do
+ time = Time.new
+ Chef::Log::Formatter.show_time = true
+ @formatter.call("monkey", time, "test", "mos def").should == "[#{time.rfc2822}] monkey: mos def\n"
+ end
+
+ it "should allow you to turn the time on and off in the output" do
+ Chef::Log::Formatter.show_time = false
+ @formatter.call("monkey", Time.new, "test", "mos def").should == "monkey: mos def\n"
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/log_spec.rb b/chef/spec/unit/log_spec.rb
new file mode 100644
index 0000000000..14892b1af4
--- /dev/null
+++ b/chef/spec/unit/log_spec.rb
@@ -0,0 +1,62 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'tempfile'
+require 'logger'
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Log do
+ it "should accept regular options to Logger.new via init" do
+ tf = Tempfile.new("chef-test-log")
+ tf.open
+ lambda { Chef::Log.init(STDOUT) }.should_not raise_error
+ lambda { Chef::Log.init(tf) }.should_not raise_error
+ end
+
+ it "should set the log level with :debug, :info, :warn, :error, or :fatal" do
+ levels = {
+ :debug => Logger::DEBUG,
+ :info => Logger::INFO,
+ :warn => Logger::WARN,
+ :error => Logger::ERROR,
+ :fatal => Logger::FATAL
+ }
+ levels.each do |symbol, constant|
+ Chef::Log.level(symbol)
+ Chef::Log.logger.level.should == constant
+ end
+ end
+
+ it "should raise an ArgumentError if you try and set the level to something strange" do
+ lambda { Chef::Log.level(:the_roots) }.should raise_error(ArgumentError)
+ end
+
+ it "should pass other method calls directly to logger" do
+ Chef::Log.level(:debug)
+ Chef::Log.should be_debug
+ lambda { Chef::Log.debug("Gimme some sugar!") }.should_not raise_error
+ end
+
+ it "should default to STDOUT if init is called with no arguments" do
+ logger_mock = mock(Logger, :null_object => true)
+ Logger.stub!(:new).and_return(logger_mock)
+ Logger.should_receive(:new).with(STDOUT).and_return(logger_mock)
+ Chef::Log.init
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/mixin/params_validate_spec.rb b/chef/spec/unit/mixin/params_validate_spec.rb
new file mode 100644
index 0000000000..937589b059
--- /dev/null
+++ b/chef/spec/unit/mixin/params_validate_spec.rb
@@ -0,0 +1,331 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+class TinyClass
+ include Chef::Mixin::ParamsValidate
+
+ def music(is_good=true)
+ is_good
+ end
+end
+
+describe Chef::Mixin::ParamsValidate do
+ before(:each) do
+ @vo = TinyClass.new()
+ end
+
+ it "should allow a hash and a hash as arguments to validate" do
+ lambda { @vo.validate({:one => "two"}, {}) }.should_not raise_error(ArgumentError)
+ end
+
+ it "should raise an argument error if validate is called incorrectly" do
+ lambda { @vo.validate("one", "two") }.should raise_error(ArgumentError)
+ end
+
+ it "should require validation map keys to be symbols or strings" do
+ lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError)
+ lambda { @vo.validate({:one => "two"}, { "one" => true }) }.should_not raise_error(ArgumentError)
+ lambda { @vo.validate({:one => "two"}, { Hash.new => true }) }.should raise_error(ArgumentError)
+ end
+
+ it "should allow options to be required with true" do
+ lambda { @vo.validate({:one => "two"}, { :one => true }) }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow options to be optional with false" do
+ lambda { @vo.validate({}, {:one => false})}.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow you to check what kind_of? thing an argument is with kind_of" do
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :kind_of => String
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :kind_of => Array
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify an argument is required with required" do
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :required => true
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:two => "string"},
+ {
+ :one => {
+ :required => true
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:two => "string"},
+ {
+ :one => {
+ :required => false
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether an object has a method with respond_to" do
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => "validate"
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => "monkey"
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to specify whether an object has all the given methods with respond_to and an array" do
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => ["validate", "music"]
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ {:one => @vo},
+ {
+ :one => {
+ :respond_to => ["monkey", "validate"]
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should let you set a default value with default => value" do
+ arguments = Hash.new
+ @vo.validate(arguments, {
+ :one => {
+ :default => "is the loneliest number"
+ }
+ })
+ arguments[:one].should == "is the loneliest number"
+ end
+
+ it "should let you check regular expressions" do
+ lambda {
+ @vo.validate(
+ { :one => "is good" },
+ {
+ :one => {
+ :regex => /^is good$/
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ { :one => "is good" },
+ {
+ :one => {
+ :regex => /^is bad$/
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should let you specify your own callbacks" do
+ lambda {
+ @vo.validate(
+ { :one => "is good" },
+ {
+ :one => {
+ :callbacks => {
+ "should be equal to is good" => lambda { |a|
+ a == "is good"
+ },
+ }
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+
+ lambda {
+ @vo.validate(
+ { :one => "is bad" },
+ {
+ :one => {
+ :callbacks => {
+ "should be equal to 'is good'" => lambda { |a|
+ a == "is good"
+ },
+ }
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should let you combine checks" do
+ args = { :one => "is good", :two => "is bad" }
+ lambda {
+ @vo.validate(
+ args,
+ {
+ :one => {
+ :kind_of => String,
+ :respond_to => [ :to_s, :upcase ],
+ :regex => /^is good/,
+ :callbacks => {
+ "should be your friend" => lambda { |a|
+ a == "is good"
+ }
+ },
+ :required => true
+ },
+ :two => {
+ :kind_of => String,
+ :required => false
+ },
+ :three => { :default => "neato mosquito" }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ args[:three].should == "neato mosquito"
+ lambda {
+ @vo.validate(
+ args,
+ {
+ :one => {
+ :kind_of => String,
+ :respond_to => [ :to_s, :upcase ],
+ :regex => /^is good/,
+ :callbacks => {
+ "should be your friend" => lambda { |a|
+ a == "is good"
+ }
+ },
+ :required => true
+ },
+ :two => {
+ :kind_of => Hash,
+ :required => false
+ },
+ :three => { :default => "neato mosquito" }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an ArgumentError if the validation map has an unknown check" do
+ lambda { @vo.validate(
+ { :one => "two" },
+ {
+ :one => {
+ :busted => "check"
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should accept keys that are strings in the options" do
+ lambda {
+ @vo.validate({ "one" => "two" }, { :one => { :regex => /^two$/ }})
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "should allow an array to kind_of" do
+ lambda {
+ @vo.validate(
+ {:one => "string"},
+ {
+ :one => {
+ :kind_of => [ String, Array ]
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ lambda {
+ @vo.validate(
+ {:one => ["string"]},
+ {
+ :one => {
+ :kind_of => [ String, Array ]
+ }
+ }
+ )
+ }.should_not raise_error(ArgumentError)
+ lambda {
+ @vo.validate(
+ {:one => Hash.new},
+ {
+ :one => {
+ :kind_of => [ String, Array ]
+ }
+ }
+ )
+ }.should raise_error(ArgumentError)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/mixin/template_spec.rb b/chef/spec/unit/mixin/template_spec.rb
new file mode 100644
index 0000000000..0ff929e8f6
--- /dev/null
+++ b/chef/spec/unit/mixin/template_spec.rb
@@ -0,0 +1,60 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+class TinyTemplateClass; include Chef::Mixin::Template; end
+
+describe Chef::Mixin::Template, "render_template" do
+
+ before(:each) do
+ @template = "abcnews"
+ @context = { :fine => "dear" }
+ @eruby = mock(:erubis, { :evaluate => "elvis costello" })
+ Erubis::Eruby.stub!(:new).and_return(@eruby)
+ @tempfile = mock(:tempfile, { :print => true, :close => true })
+ Tempfile.stub!(:new).and_return(@tempfile)
+ @tiny_template = TinyTemplateClass.new
+ end
+
+ it "should create a new Erubis object from the template" do
+ Erubis::Eruby.should_receive(:new).with("abcnews").and_return(@eruby)
+ @tiny_template.render_template(@template, @context)
+ end
+
+ it "should evaluate the template with the provided context" do
+ @eruby.should_receive(:evaluate).with(@context).and_return(true)
+ @tiny_template.render_template(@template, @context)
+ end
+
+ it "should create a tempfile for the resulting file" do
+ Tempfile.should_receive(:new).and_return(@tempfile)
+ @tiny_template.render_template(@template, @context)
+ end
+
+ it "should print the contents of the resulting template to the tempfile" do
+ @tempfile.should_receive(:print).with("elvis costello").and_return(true)
+ @tiny_template.render_template(@template, @context)
+ end
+
+ it "should close the tempfile" do
+ @tempfile.should_receive(:close).and_return(true)
+ @tiny_template.render_template(@template, @context)
+ end
+end
+
diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb
new file mode 100644
index 0000000000..605bb8b628
--- /dev/null
+++ b/chef/spec/unit/node_spec.rb
@@ -0,0 +1,326 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Node, "new method" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should create a new Chef::Node" do
+ @node.should be_a_kind_of(Chef::Node)
+ end
+end
+
+describe Chef::Node, "name" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should allow you to set a name with name(something)" do
+ lambda { @node.name("latte") }.should_not raise_error
+ end
+
+ it "should return the name with name()" do
+ @node.name("latte")
+ @node.name.should eql("latte")
+ end
+
+ it "should always have a string for name" do
+ lambda { @node.name(Hash.new) }.should raise_error(ArgumentError)
+ end
+
+end
+
+describe Chef::Node, "attributes" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should have attributes" do
+ @node.attribute.should be_a_kind_of(Hash)
+ end
+
+ it "should allow attributes to be accessed by name or symbol directly on node[]" do
+ @node.attribute["locust"] = "something"
+ @node[:locust].should eql("something")
+ @node["locust"].should eql("something")
+ end
+
+ it "should return nil if it cannot find an attribute with node[]" do
+ @node["secret"].should eql(nil)
+ end
+
+ it "should allow you to set an attribute via node[]=" do
+ @node["secret"] = "shush"
+ @node["secret"].should eql("shush")
+ end
+
+ it "should allow you to query whether an attribute exists with attribute?" do
+ @node.attribute["locust"] = "something"
+ @node.attribute?("locust").should eql(true)
+ @node.attribute?("no dice").should eql(false)
+ end
+
+ it "should allow you to set an attribute via method_missing" do
+ @node.sunshine "is bright"
+ @node.attribute[:sunshine].should eql("is bright")
+ end
+
+ it "should allow you get get an attribute via method_missing" do
+ @node.sunshine "is bright"
+ @node.sunshine.should eql("is bright")
+ end
+
+ it "should raise an ArgumentError if you ask for an attribute that doesn't exist via method_missing" do
+ lambda { @node.sunshine }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to iterate over attributes with each_attribute" do
+ @node.sunshine "is bright"
+ @node.canada "is a nice place"
+ seen_attributes = Hash.new
+ @node.each_attribute do |a,v|
+ seen_attributes[a] = v
+ end
+ seen_attributes.should have_key(:sunshine)
+ seen_attributes.should have_key(:canada)
+ seen_attributes[:sunshine].should == "is bright"
+ seen_attributes[:canada].should == "is a nice place"
+ end
+
+end
+
+describe Chef::Node, "recipes" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should have an array of recipes that should be applied" do
+ @node.recipes.should be_a_kind_of(Array)
+ end
+
+ it "should allow you to query whether or not it has a recipe applied with recipe?" do
+ @node.recipes << "sunrise"
+ @node.recipe?("sunrise").should eql(true)
+ @node.recipe?("not at home").should eql(false)
+ end
+
+ it "should allow you to set recipes with arguments" do
+ @node.recipes "one", "two"
+ @node.recipe?("one").should eql(true)
+ @node.recipe?("two").should eql(true)
+ end
+
+end
+
+describe Chef::Node, "from file" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should load a node from a ruby file" do
+ @node.from_file(File.join(File.dirname(__FILE__), "..", "data", "nodes", "test.rb"))
+ @node.name.should eql("test.example.com short")
+ @node.sunshine.should eql("in")
+ @node.something.should eql("else")
+ @node.recipes.should eql(["operations-master", "operations-monitoring"])
+ end
+
+ it "should raise an exception if the file cannot be found or read" do
+ lambda { @node.from_file("/tmp/monkeydiving") }.should raise_error(IOError)
+ end
+end
+
+describe Chef::Node, "find_file" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should load a node from a file by fqdn" do
+ @node.find_file("test.example.com")
+ @node.name.should == "test.example.com"
+ end
+
+ it "should load a node from a file by hostname" do
+ File.stub!(:exists?).and_return(true)
+ File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false)
+ @node.find_file("test.example.com")
+ @node.name.should == "test.example.com short"
+ end
+
+ it "should load a node from the default file" do
+ File.stub!(:exists?).and_return(true)
+ File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false)
+ File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.rb")).and_return(false)
+ @node.find_file("test.example.com")
+ @node.name.should == "test.example.com default"
+ end
+
+ it "should raise an ArgumentError if it cannot find any node file at all" do
+ File.stub!(:exists?).and_return(true)
+ File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.example.com.rb")).and_return(false)
+ File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "test.rb")).and_return(false)
+ File.should_receive(:exists?).with(File.join(Chef::Config[:node_path], "default.rb")).and_return(false)
+ lambda { @node.find_file("test.example.com") }.should raise_error(ArgumentError)
+ end
+end
+
+describe Chef::Node, "json" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should serialize itself as json" do
+ @node.find_file("test.example.com")
+ json = @node.to_json()
+ json.should =~ /json_class/
+ json.should =~ /name/
+ json.should =~ /attributes/
+ json.should =~ /recipes/
+ end
+
+ it "should deserialize itself from json" do
+ @node.find_file("test.example.com")
+ json = @node.to_json
+ serialized_node = JSON.parse(json)
+ serialized_node.should be_a_kind_of(Chef::Node)
+ serialized_node.name.should eql(@node.name)
+ @node.each_attribute do |k,v|
+ serialized_node[k].should eql(v)
+ end
+ serialized_node.recipes.should eql(@node.recipes)
+ end
+end
+
+describe Chef::Node, "to_index" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ @node.foo("bar")
+ end
+
+ it "should return a hash with :index attributes" do
+ @node.name("airplane")
+ @node.to_index.should == { :foo => "bar", :index_name => "node", :id => "node_airplane", :name => "airplane" }
+ end
+end
+
+
+describe Chef::Node, "to_s" do
+ before(:each) do
+ Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes"))
+ @node = Chef::Node.new()
+ end
+
+ it "should turn into a string like node[name]" do
+ @node.name("airplane")
+ @node.to_s.should eql("node[airplane]")
+ end
+end
+
+describe Chef::Node, "list" do
+ before(:each) do
+ mock_couch = mock("Chef::CouchDB")
+ mock_couch.stub!(:list).and_return(
+ {
+ "rows" => [
+ {
+ "value" => "a",
+ "key" => "avenue"
+ }
+ ]
+ }
+ )
+ Chef::CouchDB.stub!(:new).and_return(mock_couch)
+ end
+
+ it "should retrieve a list of nodes from CouchDB" do
+ Chef::Node.list.should eql(["avenue"])
+ end
+
+ it "should return just the ids if inflate is false" do
+ Chef::Node.list(false).should eql(["avenue"])
+ end
+
+ it "should return the full objects if inflate is true" do
+ Chef::Node.list(true).should eql(["a"])
+ end
+end
+
+describe Chef::Node, "load" do
+ it "should load a node from couchdb by name" do
+ mock_couch = mock("Chef::CouchDB")
+ mock_couch.should_receive(:load).with("node", "coffee").and_return(true)
+ Chef::CouchDB.stub!(:new).and_return(mock_couch)
+ Chef::Node.load("coffee")
+ end
+end
+
+describe Chef::Node, "destroy" do
+ it "should delete this node from couchdb" do
+ mock_couch = mock("Chef::CouchDB")
+ mock_couch.should_receive(:delete).with("node", "bob", 1).and_return(true)
+ Chef::CouchDB.stub!(:new).and_return(mock_couch)
+ node = Chef::Node.new
+ node.name "bob"
+ node.couchdb_rev = 1
+ Chef::Queue.should_receive(:send_msg).with(:queue, :remove, node)
+ node.destroy
+ end
+end
+
+describe Chef::Node, "save" do
+ before(:each) do
+ @mock_couch = mock("Chef::CouchDB")
+ @mock_couch.stub!(:store).and_return({ "rev" => 33 })
+ Chef::CouchDB.stub!(:new).and_return(@mock_couch)
+ Chef::Queue.stub!(:send_msg).and_return(true)
+ @node = Chef::Node.new
+ @node.name "bob"
+ @node.couchdb_rev = 1
+ end
+
+ it "should save the node to couchdb" do
+ Chef::Queue.should_receive(:send_msg).with(:queue, :index, @node)
+ @mock_couch.should_receive(:store).with("node", "bob", @node).and_return({ "rev" => 33 })
+ @node.save
+ end
+
+ it "should store the new couchdb_rev" do
+ @node.save
+ @node.couchdb_rev.should eql(33)
+ end
+end
+
+describe Chef::Node, "create_design_document" do
+ it "should create our design document" do
+ mock_couch = mock("Chef::CouchDB")
+ mock_couch.should_receive(:create_design_document).with("nodes", Chef::Node::DESIGN_DOCUMENT)
+ Chef::CouchDB.stub!(:new).and_return(mock_couch)
+ Chef::Node.create_design_document
+ end
+end
diff --git a/chef/spec/unit/openid_registration_spec.rb b/chef/spec/unit/openid_registration_spec.rb
new file mode 100644
index 0000000000..d849369796
--- /dev/null
+++ b/chef/spec/unit/openid_registration_spec.rb
@@ -0,0 +1,153 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::OpenIDRegistration, "initialize" do
+ it "should return a new Chef::OpenIDRegistration object" do
+ Chef::OpenIDRegistration.new.should be_kind_of(Chef::OpenIDRegistration)
+ end
+end
+
+describe Chef::OpenIDRegistration, "set_password" do
+ it "should generate a salt for this object" do
+ oreg = Chef::OpenIDRegistration.new
+ oreg.salt.should eql(nil)
+ oreg.set_password("foolio")
+ oreg.salt.should_not eql(nil)
+ end
+
+ it "should encrypt the password with the salt and the plaintext password" do
+ oreg = Chef::OpenIDRegistration.new
+ oreg.set_password("foolio")
+ oreg.password.should_not eql(nil)
+ end
+end
+
+describe Chef::OpenIDRegistration, "to_json" do
+ it "should serialize itself as json" do
+ oreg = Chef::OpenIDRegistration.new
+ oreg.set_password("monkey")
+ json = oreg.to_json
+ %w{json_class chef_type name salt password validated}.each do |verify|
+ json.should =~ /#{verify}/
+ end
+ end
+end
+
+describe Chef::OpenIDRegistration, "from_json" do
+ it "should serialize itself as json" do
+ oreg = Chef::OpenIDRegistration.new()
+ oreg.name = "foobar"
+ oreg.set_password("monkey")
+ oreg_json = oreg.to_json
+ nreg = JSON.parse(oreg_json)
+ nreg.should be_a_kind_of(Chef::OpenIDRegistration)
+ %w{name salt password validated}.each do |verify|
+ nreg.send(verify.to_sym).should eql(oreg.send(verify.to_sym))
+ end
+ end
+end
+
+describe Chef::OpenIDRegistration, "list" do
+ before(:each) do
+ @mock_couch = mock("Chef::CouchDB")
+ @mock_couch.stub!(:list).and_return({
+ "rows" => [
+ {
+ "value" => "a",
+ "key" => "avenue"
+ }
+ ]
+ })
+ Chef::CouchDB.stub!(:new).and_return(@mock_couch)
+ end
+
+ it "should retrieve a list of nodes from CouchDB" do
+ Chef::OpenIDRegistration.list.should eql(["avenue"])
+ end
+
+ it "should return just the ids if inflate is false" do
+ Chef::OpenIDRegistration.list(false).should eql(["avenue"])
+ end
+
+ it "should return the full objects if inflate is true" do
+ Chef::OpenIDRegistration.list(true).should eql(["a"])
+ end
+end
+
+describe Chef::OpenIDRegistration, "load" do
+ it "should load a registration from couchdb by name" do
+ @mock_couch = mock("Chef::CouchDB")
+ Chef::CouchDB.stub!(:new).and_return(@mock_couch)
+ @mock_couch.should_receive(:load).with("openid_registration", "coffee").and_return(true)
+ Chef::OpenIDRegistration.load("coffee")
+ end
+end
+
+describe Chef::OpenIDRegistration, "destroy" do
+ it "should delete this registration from couchdb" do
+ @mock_couch = mock("Chef::CouchDB")
+ @mock_couch.should_receive(:delete).with("openid_registration", "bob", 1).and_return(true)
+ Chef::CouchDB.stub!(:new).and_return(@mock_couch)
+ reg = Chef::OpenIDRegistration.new
+ reg.name = "bob"
+ reg.couchdb_rev = 1
+ reg.destroy
+ end
+end
+
+describe Chef::OpenIDRegistration, "save" do
+ before(:each) do
+ @mock_couch = mock("Chef::CouchDB")
+ Chef::CouchDB.stub!(:new).and_return(@mock_couch)
+ @reg = Chef::OpenIDRegistration.new
+ @reg.name = "bob"
+ @reg.couchdb_rev = 1
+ end
+
+ it "should save the registration to couchdb" do
+ @mock_couch.should_receive(:store).with("openid_registration", "bob", @reg).and_return({ "rev" => 33 })
+ @reg.save
+ end
+
+ it "should store the new couchdb_rev" do
+ @mock_couch.stub!(:store).with("openid_registration", "bob", @reg).and_return({ "rev" => 33 })
+ @reg.save
+ @reg.couchdb_rev.should eql(33)
+ end
+end
+
+describe Chef::OpenIDRegistration, "create_design_document" do
+ it "should create our design document" do
+ mock_couch = mock("Chef::CouchDB")
+ mock_couch.should_receive(:create_design_document).with("registrations", Chef::OpenIDRegistration::DESIGN_DOCUMENT)
+ Chef::CouchDB.stub!(:new).and_return(mock_couch)
+ Chef::OpenIDRegistration.create_design_document
+ end
+end
+
+describe Chef::OpenIDRegistration, "has_key?" do
+ it "should check with CouchDB for a registration with this key" do
+ @mock_couch = mock("Chef::CouchDB")
+ @mock_couch.should_receive(:has_key?).with("openid_registration", "bob").and_return(true)
+ Chef::CouchDB.stub!(:new).and_return(@mock_couch)
+ Chef::OpenIDRegistration.has_key?("bob")
+ end
+end
+
diff --git a/chef/spec/unit/platform_spec.rb b/chef/spec/unit/platform_spec.rb
new file mode 100644
index 0000000000..4eeeebed89
--- /dev/null
+++ b/chef/spec/unit/platform_spec.rb
@@ -0,0 +1,209 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Platform do
+ before(:each) do
+ Chef::Platform.platforms = {
+ :darwin => {
+ "9.2.2" => {
+ :file => "darwinian",
+ :else => "thing"
+ },
+ :default => {
+ :file => "old school",
+ :snicker => "snack"
+ }
+ },
+ :mars_volta => {
+ },
+ :default => {
+ :file => Chef::Provider::File,
+ :pax => "brittania",
+ :cat => "nice"
+ }
+ }
+ end
+
+ it "should allow you to look up a platform by name and version, returning the provider map for it" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("darwinian")
+ end
+
+ it "should use the default providers for an os if the specific version does not exist" do
+ pmap = Chef::Platform.find("Darwin", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql("old school")
+ end
+
+ it "should use the default providers if the os doesn't give me a default, but does exist" do
+ pmap = Chef::Platform.find("mars_volta", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql(Chef::Provider::File)
+ end
+
+ it "should use the default provider if the os does not exist" do
+ pmap = Chef::Platform.find("AIX", "1")
+ pmap.should be_a_kind_of(Hash)
+ pmap[:file].should eql(Chef::Provider::File)
+ end
+
+ it "should merge the defaults for an os with the specific version" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap[:file].should eql("darwinian")
+ pmap[:snicker].should eql("snack")
+ end
+
+ it "should merge the defaults for an os with the universal defaults" do
+ pmap = Chef::Platform.find("Darwin", "9.2.2")
+ pmap[:file].should eql("darwinian")
+ pmap[:pax].should eql("brittania")
+ end
+
+ it "should allow you to look up a provider for a platform directly by symbol" do
+ Chef::Platform.find_provider("Darwin", "9.2.2", :file).should eql("darwinian")
+ end
+
+ it "should raise an exception if a provider cannot be found for a resource type" do
+ lambda { Chef::Platform.find_provider("Darwin", "9.2.2", :coffee) }.should raise_error(ArgumentError)
+ end
+
+ it "should look up a provider for a resource with a Chef::Resource object" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ Chef::Platform.find_provider("Darwin", "9.2.2", kitty)
+ end
+
+ it "should look up a provider with a node and a Chef::Resource object" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.operatingsystem("Darwin")
+ node.operatingsystemversion("9.2.2")
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("nice")
+ end
+
+ it "should prefer lsbdistid over operatingsystem when looking up via node" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.operatingsystem("Darwin")
+ node.operatingsystemversion("9.2.2")
+ node.lsbdistid("Not Linux")
+ Chef::Platform.set(
+ :platform => :not_linux,
+ :resource => :cat,
+ :provider => "bourbon"
+ )
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon")
+ end
+
+ it "should prefer macosx_productnmae over operatingsystem when looking up via node" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.operatingsystem("Darwin")
+ node.operatingsystemversion("9.2.2")
+ node.macosx_productname("Mac OS X")
+ Chef::Platform.set(
+ :platform => :mac_os_x,
+ :resource => :cat,
+ :provider => "bourbon"
+ )
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon")
+ end
+
+ it "should prefer lsbdistrelease over operatingsystem when looking up via node" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.operatingsystem("Darwin")
+ node.operatingsystemversion("9.2.2")
+ node.lsbdistrelease("10")
+ Chef::Platform.set(
+ :platform => :darwin,
+ :version => "10",
+ :resource => :cat,
+ :provider => "bourbon"
+ )
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon")
+ end
+
+ it "should prefer macosx_productversion over operatingsystem when looking up via node" do
+ kitty = Chef::Resource::Cat.new("loulou")
+ node = Chef::Node.new
+ node.name("Intel")
+ node.operatingsystem("Darwin")
+ node.operatingsystemversion("9.2.2")
+ node.macosx_productversion("10")
+ Chef::Platform.set(
+ :platform => :darwin,
+ :version => "10",
+ :resource => :cat,
+ :provider => "bourbon"
+ )
+ Chef::Platform.find_provider_for_node(node, kitty).should eql("bourbon")
+ end
+
+ it "should update the provider map with map" do
+ Chef::Platform.set(
+ :platform => :darwin,
+ :version => "9.2.2",
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:darwin]["9.2.2"][:file].should eql("masterful")
+ Chef::Platform.set(
+ :platform => :darwin,
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:darwin][:default][:file].should eql("masterful")
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.set(
+ :platform => :hero,
+ :version => "9.2.2",
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:hero]["9.2.2"][:file].should eql("masterful")
+
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ Chef::Platform.platforms = {}
+
+ Chef::Platform.set(
+ :resource => :file,
+ :provider => "masterful"
+ )
+ Chef::Platform.platforms[:default][:file].should eql("masterful")
+
+ end
+
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/provider/directory_spec.rb b/chef/spec/unit/provider/directory_spec.rb
new file mode 100644
index 0000000000..4978b27288
--- /dev/null
+++ b/chef/spec/unit/provider/directory_spec.rb
@@ -0,0 +1,98 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'ostruct'
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Provider::Directory do
+ before(:each) do
+ @new_resource = mock("New Resource", :null_object => true)
+ @new_resource.stub!(:name).and_return("directory")
+ @new_resource.stub!(:path).and_return("/tmp")
+ @new_resource.stub!(:owner).and_return(500)
+ @new_resource.stub!(:group).and_return(500)
+ @new_resource.stub!(:mode).and_return(0644)
+ @new_resource.stub!(:updated).and_return(false)
+ @node = Chef::Node.new
+ @node.name "latte"
+ @directory = Chef::Provider::Directory.new(@node, @new_resource)
+ end
+
+ it "should load the current resource based on the new resource" do
+ File.should_receive(:exist?).once.and_return(true)
+ File.should_receive(:directory?).once.and_return(true)
+ cstats = mock("stats", :null_object => true)
+ cstats.stub!(:uid).and_return(500)
+ cstats.stub!(:gid).and_return(500)
+ cstats.stub!(:mode).and_return(0755)
+ File.should_receive(:stat).once.and_return(cstats)
+ @directory.load_current_resource
+ @directory.current_resource.path.should eql(@new_resource.path)
+ @directory.current_resource.owner.should eql(500)
+ @directory.current_resource.group.should eql(500)
+ @directory.current_resource.mode.should eql("755")
+ end
+
+ it "should create a new directory on create, setting updated to true" do
+ load_mock_provider
+ File.should_receive(:exists?).once.and_return(false)
+ Dir.should_receive(:mkdir).with(@new_resource.path).once.and_return(true)
+ @directory.new_resource.should_receive(:updated=).with(true)
+ @directory.should_receive(:set_owner).once.and_return(true)
+ @directory.should_receive(:set_group).once.and_return(true)
+ @directory.should_receive(:set_mode).once.and_return(true)
+ @directory.action_create
+ end
+
+ it "should not create the directory if it already exists" do
+ load_mock_provider
+ File.should_receive(:exists?).once.and_return(true)
+ Dir.should_not_receive(:mkdir).with(@new_resource.path)
+ @directory.stub!(:set_owner).and_return(true)
+ @directory.stub!(:set_group).and_return(true)
+ @directory.stub!(:set_mode).and_return(true)
+ @directory.action_create
+ end
+
+ it "should delete the directory if it exists, and is writable with action_delete" do
+ load_mock_provider
+ File.should_receive(:exists?).once.and_return(true)
+ File.should_receive(:writable?).once.and_return(true)
+ Dir.should_receive(:delete).with(@new_resource.path).once.and_return(true)
+ @directory.action_delete
+ end
+
+ it "should raise an exception if it cannot delete the file due to bad permissions" do
+ load_mock_provider
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:writable?).and_return(false)
+ lambda { @directory.action_delete }.should raise_error(RuntimeError)
+ end
+
+ def load_mock_provider
+ File.stub!(:exist?).and_return(true)
+ File.stub!(:directory?).and_return(true)
+ cstats = mock("stats", :null_object => true)
+ cstats.stub!(:uid).and_return(500)
+ cstats.stub!(:gid).and_return(500)
+ cstats.stub!(:mode).and_return(0755)
+ File.stub!(:stat).once.and_return(cstats)
+ @directory.load_current_resource
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/provider/file_spec.rb b/chef/spec/unit/provider/file_spec.rb
new file mode 100644
index 0000000000..9ea628a357
--- /dev/null
+++ b/chef/spec/unit/provider/file_spec.rb
@@ -0,0 +1,224 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'ostruct'
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Provider::File do
+ before(:each) do
+ @resource = Chef::Resource::File.new("seattle")
+ @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt"))
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider::File.new(@node, @resource)
+ end
+
+ it "should return a Chef::Provider::File" do
+ @provider.should be_a_kind_of(Chef::Provider::File)
+ end
+
+ it "should store the resource passed to new as new_resource" do
+ @provider.new_resource.should eql(@resource)
+ end
+
+ it "should store the node passed to new as node" do
+ @provider.node.should eql(@node)
+ end
+
+ it "should load a current resource based on the one specified at construction" do
+ @provider.load_current_resource
+ @provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+ @provider.current_resource.name.should eql(@resource.name)
+ @provider.current_resource.path.should eql(@resource.path)
+ @provider.current_resource.owner.should_not eql(nil)
+ @provider.current_resource.group.should_not eql(nil)
+ @provider.current_resource.mode.should_not eql(nil)
+ end
+
+ it "should load a mostly blank current resource if the file specified in new_resource doesn't exist/isn't readable" do
+ resource = Chef::Resource::File.new("seattle")
+ resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "woot.txt"))
+ node = Chef::Node.new
+ node.name "latte"
+ provider = Chef::Provider::File.new(node, resource)
+ provider.load_current_resource
+ provider.current_resource.should be_a_kind_of(Chef::Resource::File)
+ provider.current_resource.name.should eql(resource.name)
+ provider.current_resource.path.should eql(resource.path)
+ provider.current_resource.owner.should eql(nil)
+ provider.current_resource.group.should eql(nil)
+ provider.current_resource.mode.should eql(nil)
+ end
+
+ it "should load the correct value for owner of the current resource" do
+ stats = File.stat(@resource.path)
+ @provider.load_current_resource
+ @provider.current_resource.owner.should eql(stats.uid)
+ end
+
+ it "should load an md5 sum for an existing file" do
+ @provider.load_current_resource
+ @provider.current_resource.checksum("8d6152c7d62ea9188eda596c4d31e732")
+ end
+
+ it "should compare the current owner with the requested owner" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:owner).and_return("adam")
+ Etc.stub!(:getpwnam).and_return(
+ OpenStruct.new(
+ :name => "adam",
+ :passwd => "foo",
+ :uid => 501,
+ :gid => 501,
+ :gecos => "Adam Jacob",
+ :dir => "/Users/adam",
+ :shell => "/bin/zsh",
+ :change => "0",
+ :uclass => "",
+ :expire => 0
+ )
+ )
+ @provider.current_resource.owner(501)
+ @provider.compare_owner.should eql(true)
+
+ @provider.current_resource.owner(777)
+ @provider.compare_owner.should eql(false)
+
+ @provider.new_resource.stub!(:owner).and_return(501)
+ @provider.current_resource.owner(501)
+ @provider.compare_owner.should eql(true)
+
+ @provider.new_resource.stub!(:owner).and_return("501")
+ @provider.current_resource.owner(501)
+ @provider.compare_owner.should eql(true)
+ end
+
+ it "should set the ownership on the file to the requested owner" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:owner).and_return(9982398)
+ File.stub!(:chown).and_return(1)
+ File.should_receive(:chown).with(9982398, nil, @provider.current_resource.path)
+ lambda { @provider.set_owner }.should_not raise_error
+ end
+
+ it "should raise an exception if you are not root and try to change ownership" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:owner).and_return(0)
+ if Process.uid != 0
+ lambda { @provider.set_owner }.should raise_error
+ end
+ end
+
+ it "should compare the current group with the requested group" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:group).and_return("adam")
+ Etc.stub!(:getgrnam).and_return(
+ OpenStruct.new(
+ :name => "adam",
+ :gid => 501
+ )
+ )
+ @provider.current_resource.group(501)
+ @provider.compare_group.should eql(true)
+
+ @provider.current_resource.group(777)
+ @provider.compare_group.should eql(false)
+
+ @provider.new_resource.stub!(:group).and_return(501)
+ @provider.current_resource.group(501)
+ @provider.compare_group.should eql(true)
+
+ @provider.new_resource.stub!(:group).and_return("501")
+ @provider.current_resource.group(501)
+ @provider.compare_group.should eql(true)
+ end
+
+ it "should set the group on the file to the requested group" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:group).and_return(9982398)
+ File.stub!(:chown).and_return(1)
+ File.should_receive(:chown).with(nil, 9982398, @provider.current_resource.path)
+ lambda { @provider.set_group }.should_not raise_error
+ end
+
+ it "should raise an exception if you are not root and try to change the group" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:group).and_return(0)
+ if Process.uid != 0
+ lambda { @provider.set_group }.should raise_error
+ end
+ end
+
+ it "should create the file if it is missing, then set the attributes on action_create" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:owner).and_return(9982398)
+ @provider.new_resource.stub!(:group).and_return(9982398)
+ @provider.new_resource.stub!(:mode).and_return(0755)
+ @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo")
+ File.stub!(:chown).and_return(1)
+ File.should_receive(:chown).with(nil, 9982398, @provider.new_resource.path)
+ File.stub!(:chown).and_return(1)
+ File.should_receive(:chown).with(9982398, nil, @provider.new_resource.path)
+ File.stub!(:open).and_return(1)
+ File.should_receive(:chmod).with(0755, @provider.new_resource.path).and_return(1)
+ File.should_receive(:open).with(@provider.new_resource.path, "w+")
+ @provider.action_create
+ end
+
+ it "should delete the file if it exists and is writable on action_delete" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo")
+ @provider.stub!(:backup).and_return(true)
+ File.should_receive("exists?").with(@provider.new_resource.path).and_return(true)
+ File.should_receive("writable?").with(@provider.new_resource.path).and_return(true)
+ File.should_receive(:delete).with(@provider.new_resource.path).and_return(true)
+ @provider.action_delete
+ end
+
+ it "should raise an error if it cannot delete the file" do
+ @provider.load_current_resource
+ @provider.stub!(:backup).and_return(true)
+ @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo")
+ File.should_receive("exists?").with(@provider.new_resource.path).and_return(false)
+ lambda { @provider.action_delete }.should raise_error()
+ end
+
+ it "should update the atime/mtime on action_touch" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/monkeyfoo")
+ File.should_receive(:utime).once.and_return(1)
+ File.stub!(:open).and_return(1)
+ File.stub!(:chown).and_return(1)
+ File.stub!(:chmod).and_return(1)
+ @provider.action_touch
+ end
+
+ it "should backup a file no more than :backup times" do
+ @provider.load_current_resource
+ @provider.new_resource.stub!(:path).and_return("/tmp/s-20080705111233")
+ @provider.new_resource.stub!(:backup).and_return(2)
+ Dir.stub!(:[]).and_return([ "/tmp/s-20080705111233", "/tmp/s-20080705111232", "/tmp/s-20080705111223"])
+ FileUtils.should_receive(:rm).with("/tmp/s-20080705111232").once.and_return(true)
+ FileUtils.should_receive(:rm).with("/tmp/s-20080705111223").once.and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ File.stub!(:exist?).and_return(true)
+ @provider.backup
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/provider/link_spec.rb b/chef/spec/unit/provider/link_spec.rb
new file mode 100644
index 0000000000..462ab9409b
--- /dev/null
+++ b/chef/spec/unit/provider/link_spec.rb
@@ -0,0 +1,147 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 'ostruct'
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Provider::Link do
+ before(:each) do
+ @new_resource = mock("New Resource", :null_object => true)
+ @new_resource.stub!(:name).and_return("symlink")
+ @new_resource.stub!(:source_file).and_return("/tmp/fofile")
+ @new_resource.stub!(:target_file).and_return("/tmp/fofile-link")
+ @new_resource.stub!(:link_type).and_return(:symbolic)
+ @new_resource.stub!(:updated).and_return(false)
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider::Link.new(@node, @new_resource)
+ end
+
+ it "should load the current resource based on the new resource" do
+ File.should_receive(:exists?).once.and_return(true)
+ File.should_receive(:symlink?).once.and_return(true)
+ File.should_receive(:readlink).once.and_return("/tmp/fofile")
+ @provider.load_current_resource
+ @provider.current_resource.name.should eql("symlink")
+ @provider.current_resource.source_file.should eql("/tmp/fofile")
+ @provider.current_resource.target_file.should eql("/tmp/fofile-link")
+ @provider.current_resource.link_type.should eql(:symbolic)
+ end
+
+ it "should set the current resource's source_file to '' if the target_file doesn't exist" do
+ File.should_receive(:exists?).once.and_return(true)
+ File.should_receive(:symlink?).once.and_return(false)
+ @provider.load_current_resource
+ @provider.current_resource.source_file.should eql("")
+ end
+
+ it "should load the current resource if it is a hard link" do
+ @new_resource.stub!(:link_type).and_return(:hard)
+ File.should_receive(:exists?).twice.and_return(true)
+ cstat = mock("stats", :null_object => true)
+ cstat.stub!(:ino).and_return(1)
+ File.should_receive(:stat).with("/tmp/fofile-link").and_return(cstat)
+ File.should_receive(:stat).with("/tmp/fofile").and_return(cstat)
+ @provider.load_current_resource
+ @provider.current_resource.name.should eql("symlink")
+ @provider.current_resource.source_file.should eql("/tmp/fofile")
+ @provider.current_resource.target_file.should eql("/tmp/fofile-link")
+ @provider.current_resource.link_type.should eql(:hard)
+ end
+
+ it "should set the current resource's source_file to '' if the target_file doesn't exist" do
+ @new_resource.stub!(:link_type).and_return(:hard)
+ File.should_receive(:exists?).once.and_return(false)
+ @provider.load_current_resource
+ @provider.current_resource.source_file.should eql("")
+ end
+
+ it "should set the current resource's source_file to '' if the two files arent hardlinked" do
+ @new_resource.stub!(:link_type).and_return(:hard)
+ File.stub!(:exists?).and_return(true)
+ cstat = mock("stats", :null_object => true)
+ cstat.stub!(:ino).and_return(0)
+ bstat = mock("stats", :null_object => true)
+ bstat.stub!(:ino).and_return(1)
+ File.should_receive(:stat).with("/tmp/fofile-link").and_return(cstat)
+ File.should_receive(:stat).with("/tmp/fofile").and_return(bstat)
+ @provider.load_current_resource
+ @provider.current_resource.source_file.should eql("")
+ end
+
+ it "should create a new symlink on create, setting updated to true" do
+ load_mock_symlink_provider
+ @provider.current_resource.source_file("nil")
+ File.should_receive(:symlink).with(@new_resource.source_file, @new_resource.target_file).once.and_return(true)
+ @provider.new_resource.should_receive(:updated=).with(true)
+ @provider.action_create
+ end
+
+ it "should not create a new symlink on create if it already exists" do
+ load_mock_symlink_provider
+ File.should_not_receive(:symlink).with(@new_resource.source_file, @new_resource.target_file)
+ @provider.action_create
+ end
+
+ it "should create a new hard link on create, setting updated to true" do
+ load_mock_hardlink_provider
+ @provider.current_resource.source_file("nil")
+ File.should_receive(:link).with(@new_resource.source_file, @new_resource.target_file).once.and_return(true)
+ @provider.new_resource.should_receive(:updated=).with(true)
+ @provider.action_create
+ end
+
+ it "should not create a new hard link on create if it already exists" do
+ load_mock_symlink_provider
+ File.should_not_receive(:link).with(@new_resource.source_file, @new_resource.target_file)
+ @provider.action_create
+ end
+
+ it "should delete the link if it exists, and is writable with action_delete" do
+ load_mock_symlink_provider
+ File.should_receive(:exists?).once.and_return(true)
+ File.should_receive(:writable?).once.and_return(true)
+ File.should_receive(:delete).with(@new_resource.target_file).once.and_return(true)
+ @provider.action_delete
+ end
+
+ it "should raise an exception if it cannot delete the link due to bad permissions" do
+ load_mock_symlink_provider
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:writable?).and_return(false)
+ lambda { @provider.action_delete }.should raise_error(RuntimeError)
+ end
+
+ def load_mock_symlink_provider
+ File.stub!(:exists?).and_return(true)
+ File.stub!(:symlink?).and_return(true)
+ File.stub!(:readlink).and_return("/tmp/fofile")
+ @provider.load_current_resource
+ end
+
+ def load_mock_hardlink_provider
+ @new_resource.stub!(:link_type).and_return(:hard)
+ File.stub!(:exists?).twice.and_return(true)
+ cstat = mock("stats", :null_object => true)
+ cstat.stub!(:ino).and_return(1)
+ File.stub!(:stat).with("/tmp/fofile-link").and_return(cstat)
+ File.stub!(:stat).with("/tmp/fofile").and_return(cstat)
+ @provider.load_current_resource
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/provider/remote_file_spec.rb b/chef/spec/unit/provider/remote_file_spec.rb
new file mode 100644
index 0000000000..51c48d0f9a
--- /dev/null
+++ b/chef/spec/unit/provider/remote_file_spec.rb
@@ -0,0 +1,152 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Provider::RemoteFile, "action_create" do
+ before(:each) do
+ @resource = Chef::Resource::RemoteFile.new("seattle")
+ @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt"))
+ @resource.source("http://foo")
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider::RemoteFile.new(@node, @resource)
+ @provider.current_resource = @resource.clone
+ end
+
+ it "should call do_remote_file" do
+ @provider.should_receive(:do_remote_file).with(@resource.source, @resource.path)
+ @provider.action_create
+ end
+
+end
+
+describe Chef::Provider::RemoteFile, "do_remote_file" do
+ before(:each) do
+ @rest = mock(Chef::REST, { })
+ @tempfile = mock(Tempfile, { :path => "/tmp/foo", })
+ @rest.stub!(:get_rest).and_return(@tempfile)
+ @resource = Chef::Resource::RemoteFile.new("seattle")
+ @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt"))
+ @resource.source("foo")
+ @resource.cookbook_name = "monkey"
+ @node = Chef::Node.new
+ @node.name "latte"
+ @node.fqdn "latte.local"
+ @provider = Chef::Provider::RemoteFile.new(@node, @resource)
+ @provider.stub!(:checksum).and_return("dad86c61eea237932f201009e5431609")
+ @provider.current_resource = @resource.clone
+ @provider.current_resource.checksum("dad86c61eea237932f201009e5431609")
+ File.stub!(:exists?).and_return(true)
+ FileUtils.stub!(:cp).and_return(true)
+ Chef::Platform.stub!(:find_platform_and_version).and_return([ :mac_os_x, "10.5.1" ])
+ end
+
+ def do_remote_file
+ Chef::REST.stub!(:new).and_return(@rest)
+ @provider.do_remote_file(@resource.source, @resource.path)
+ end
+
+ it "should set the checksum if the file exists" do
+ @provider.should_receive(:checksum).with(@resource.path)
+ do_remote_file
+ end
+
+ it "should not set the checksum if the file doesn't exist" do
+ File.stub!(:exists?).with(@resource.path).and_return(false)
+ @provider.should_not_receive(:checksum).with(@resource.path)
+ do_remote_file
+ end
+
+ it "should call generate_url with the current checksum as an extra attribute" do
+ @provider.should_receive(:generate_url).with(@resource.source, "files", { :checksum => "dad86c61eea237932f201009e5431609"})
+ do_remote_file
+ end
+
+ it "should call get_rest with a correctly composed url" do
+ url = "cookbooks/#{@resource.cookbook_name}/files?id=#{@resource.source}"
+ url += "&platform=mac_os_x"
+ url += "&version=10.5.1"
+ url += "&fqdn=latte.local"
+ url += "&checksum=dad86c61eea237932f201009e5431609"
+ @rest.should_receive(:get_rest).with(url, true).and_return(@tempfile)
+ do_remote_file
+ end
+
+ it "should not transfer the file if it has not been changed" do
+ r = Net::HTTPNotModified.new("one", "two", "three")
+ e = Net::HTTPRetriableError.new("304", r)
+ @rest.stub!(:get_rest).and_raise(e)
+ do_remote_file.should eql(false)
+ end
+
+ it "should raise an exception if it's any other kind of retriable response than 304" do
+ r = Net::HTTPMovedPermanently.new("one", "two", "three")
+ e = Net::HTTPRetriableError.new("301", r)
+ @rest.stub!(:get_rest).and_raise(e)
+ lambda { do_remote_file }.should raise_error(Net::HTTPRetriableError)
+ end
+
+ it "should raise an exception if anything else happens" do
+ r = Net::HTTPBadRequest.new("one", "two", "three")
+ e = Net::HTTPServerException.new("fake exception", r)
+ @rest.stub!(:get_rest).and_raise(e)
+ lambda { do_remote_file }.should raise_error(Net::HTTPServerException)
+ end
+
+ it "should checksum the raw file" do
+ @provider.should_receive(:checksum).with(@tempfile.path).and_return("dad86c61eea237932f201009e5431608")
+ do_remote_file
+ end
+
+ it "should backup the original file" do
+ @provider.should_receive(:backup).with(@resource.path).and_return(true)
+ do_remote_file
+ end
+
+ it "should set the new resource to updated" do
+ @resource.should_receive(:updated=).with(true)
+ do_remote_file
+ end
+
+ it "should copy the raw file to the new resource" do
+ FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true)
+ do_remote_file
+ end
+
+ it "should set the owner if provided" do
+ @resource.owner("adam")
+ @provider.should_receive(:set_owner).and_return(true)
+ do_remote_file
+ end
+
+ it "should set the group if provided" do
+ @resource.group("adam")
+ @provider.should_receive(:set_group).and_return(true)
+ do_remote_file
+ end
+
+ it "should set the mode if provided" do
+ @resource.mode(0676)
+ @provider.should_receive(:set_mode).and_return(true)
+ do_remote_file
+ end
+
+# TODO: Finish these tests
+
+end
diff --git a/chef/spec/unit/provider/template_spec.rb b/chef/spec/unit/provider/template_spec.rb
new file mode 100644
index 0000000000..abc98e2ea3
--- /dev/null
+++ b/chef/spec/unit/provider/template_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Provider::Template, "action_create" do
+ before(:each) do
+ @rest = mock(Chef::REST, { :get_rest => "/tmp/foobar" })
+ @tempfile = mock(Tempfile, { :path => "/tmp/foo", })
+ @rest.stub!(:get_rest).and_return(@tempfile)
+ @resource = Chef::Resource::Template.new("seattle")
+ @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt"))
+ @resource.source("http://foo")
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider::Template.new(@node, @resource)
+ @provider.stub!(:checksum).and_return("dad86c61eea237932f201009e5431609")
+ @provider.current_resource = @resource.clone
+ @provider.current_resource.checksum("dad86c61eea237932f201009e5431609")
+ FileUtils.stub!(:cp).and_return(true)
+ end
+
+ def do_action_create
+ Chef::REST.stub!(:new).and_return(@rest)
+ @provider.action_create
+ end
+
+ it "should get the template based on the resources source value" do
+ @rest.should_receive(:get_rest).with(@resource.source, true).and_return(@tempfile)
+ do_action_create
+ end
+
+ it "should set the checksum of the new resource to the value of the returned template" do
+ @resource.should_receive(:checksum).with("dad86c61eea237932f201009e5431609").once
+ @resource.should_receive(:checksum).twice
+ do_action_create
+ end
+
+ it "should not copy the tempfile to the real file if the checksums match" do
+ FileUtils.should_not_receive(:cp)
+ do_action_create
+ end
+
+ it "should copy the tempfile to the real file if the checksums do not match" do
+ @provider.stub!(:checksum).and_return("dad86c61eea237932f201009e5431607")
+ FileUtils.should_receive(:cp).once
+ @provider.stub!(:backup).and_return(true)
+ do_action_create
+ end
+
+ it "should set the owner if provided" do
+ @resource.owner("adam")
+ @provider.should_receive(:set_owner).and_return(true)
+ do_action_create
+ end
+
+ it "should set the group if provided" do
+ @resource.group("adam")
+ @provider.should_receive(:set_group).and_return(true)
+ do_action_create
+ end
+
+ it "should set the mode if provided" do
+ @resource.mode(0676)
+ @provider.should_receive(:set_mode).and_return(true)
+ do_action_create
+ end
+end
+
+describe Chef::Provider::Template, "generate_url" do
+
+ before(:each) do
+ @resource = Chef::Resource::Template.new("seattle")
+ @resource.cookbook_name = "daft"
+ @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt"))
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider::Template.new(@node, @resource)
+ end
+
+ it "should return a raw url if it starts with http" do
+ @provider.generate_url('http://foobar', "templates").should eql("http://foobar")
+ end
+
+ it "should return a composed url if it does not start with http" do
+ Chef::Platform.stub!(:find_platform_and_version).and_return(["monkey", "1.0"])
+ @node.fqdn("monkeynode")
+ @provider.generate_url('default/something', "templates").should eql("cookbooks/daft/templates?id=default/something&platform=monkey&version=1.0&fqdn=monkeynode")
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/provider_spec.rb b/chef/spec/unit/provider_spec.rb
new file mode 100644
index 0000000000..0512012598
--- /dev/null
+++ b/chef/spec/unit/provider_spec.rb
@@ -0,0 +1,48 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Provider do
+ before(:each) do
+ @resource = Chef::Resource.new("funk")
+ @node = Chef::Node.new
+ @node.name "latte"
+ @provider = Chef::Provider.new(@node, @resource)
+ end
+
+ it "should return a Chef::Provider" do
+ @provider.should be_a_kind_of(Chef::Provider)
+ end
+
+ it "should store the resource passed to new as new_resource" do
+ @provider.new_resource.should eql(@resource)
+ end
+
+ it "should store the node passed to new as node" do
+ @provider.node.should eql(@node)
+ end
+
+ it "should have nil for current_resource by default" do
+ @provider.current_resource.should eql(nil)
+ end
+
+ it "should return true for action_nothing" do
+ @provider.action_nothing.should eql(true)
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/queue_spec.rb b/chef/spec/unit/queue_spec.rb
new file mode 100644
index 0000000000..e902f44631
--- /dev/null
+++ b/chef/spec/unit/queue_spec.rb
@@ -0,0 +1,105 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Queue do
+
+ it "should connect to a stomp server on localhost and 61613" do
+ Stomp::Connection.should_receive(:open).with("", "", "localhost", 61613, false).once
+ Chef::Queue.connect
+ end
+
+ it "should allow config options to override defaults on connect" do
+ Chef::Config[:queue_user] = "monkey"
+ Chef::Config[:queue_password] = "password"
+ Chef::Config[:queue_host] = "10.10.10.10"
+ Chef::Config[:queue_port] = 61614
+ Stomp::Connection.should_receive(:open).with("monkey", "password", "10.10.10.10", 61614, false).once
+ Chef::Queue.connect
+ end
+
+ it "should make a url based on type and name" do
+ Chef::Queue.make_url("topic", "goal").should eql("/topic/chef/goal")
+ Chef::Queue.make_url("queue", "pool").should eql("/queue/chef/pool")
+ end
+
+ it "should allow you to subscribe to a queue" do
+ queue = mock("Queue", :null_object => true)
+ queue.should_receive(:subscribe).with(Chef::Queue.make_url(:topic, :node)).once
+ Stomp::Connection.stub!(:open).and_return(queue)
+ Chef::Queue.connect
+ Chef::Queue.subscribe(:topic, :node)
+ end
+
+ it "should allow you to send a message" do
+ message = mock("Message", :null_object => true)
+ message.should_receive(:to_json).once.and_return("some json")
+ connection = mock("Connection", :null_object => true)
+ connection.should_receive(:send).with(Chef::Queue.make_url(:queue, :node), "some json").once.and_return(true)
+ Stomp::Connection.stub!(:open).and_return(connection)
+ Chef::Queue.connect
+ Chef::Queue.send_msg(:queue, :node, message)
+ end
+
+ it "should receive a message with receive_msg" do
+ raw_msg = mock("Stomp Message", :null_object => true)
+ raw_msg.should_receive(:body).twice.and_return("the body")
+ connection = mock("Connection", :null_object => true)
+ connection.should_receive(:receive).once.and_return(raw_msg)
+ JSON.should_receive(:parse).with("the body").and_return("the body")
+ Stomp::Connection.stub!(:open).and_return(connection)
+ Chef::Queue.connect
+ Chef::Queue.receive_msg.should eql([ "the body", raw_msg ])
+ end
+
+ it "should poll for a message with poll_msg, returning a message if there is one" do
+ raw_msg = mock("Stomp Message", :null_object => true)
+ raw_msg.should_receive(:body).once.and_return("the body")
+ connection = mock("Connection", :null_object => true)
+ connection.should_receive(:poll).once.and_return(raw_msg)
+ JSON.should_receive(:parse).with("the body").and_return("the body")
+ Stomp::Connection.stub!(:open).and_return(connection)
+ Chef::Queue.connect
+ Chef::Queue.poll_msg.should eql("the body")
+ end
+
+ it "should poll for a message with poll_msg, returning nil if there is not a message" do
+ connection = mock("Connection", :null_object => true)
+ connection.should_receive(:poll).once.and_return(nil)
+ JSON.should_not_receive(:parse).with(nil)
+ Stomp::Connection.stub!(:open).and_return(connection)
+ Chef::Queue.connect
+ Chef::Queue.poll_msg.should eql(nil)
+ end
+
+ it "should raise an exception if you disconnect without a connection" do
+ Stomp::Connection.stub!(:open).and_return(nil)
+ Chef::Queue.connect
+ lambda { Chef::Queue.disconnect }.should raise_error(ArgumentError)
+ end
+
+ it "should disconnect an active connection" do
+ connection = mock("Connection", :null_object => true)
+ connection.should_receive(:disconnect).once.and_return(true)
+ Stomp::Connection.stub!(:open).and_return(connection)
+ Chef::Queue.connect
+ Chef::Queue.disconnect
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/recipe_spec.rb b/chef/spec/unit/recipe_spec.rb
new file mode 100644
index 0000000000..77d5354993
--- /dev/null
+++ b/chef/spec/unit/recipe_spec.rb
@@ -0,0 +1,143 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Recipe do
+ before(:each) do
+ @recipe = Chef::Recipe.new("hjk", "test", Chef::Node.new)
+ end
+
+ it "should load a two word (zen_master) resource" do
+ lambda do
+ @recipe.zen_master "monkey" do
+ peace true
+ end
+ end.should_not raise_error(ArgumentError)
+ end
+
+ it "should load a one word (cat) resource" do
+ lambda do
+ @recipe.cat "loulou" do
+ pretty_kitty true
+ end
+ end.should_not raise_error(ArgumentError)
+ end
+
+ it "should throw an error if you access a resource that we can't find" do
+ lambda { @recipe.not_home { || } }.should raise_error(NameError)
+ end
+
+ it "should allow regular errors (not NameErrors) to pass unchanged" do
+ lambda {
+ @recipe.cat { || raise ArgumentError, "You Suck" }
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should add our zen_master to the collection" do
+ @recipe.zen_master "monkey" do
+ peace true
+ end
+ @recipe.collection.lookup("zen_master[monkey]").name.should eql("monkey")
+ end
+
+ it "should add our zen masters to the collection in the order they appear" do
+ %w{monkey dog cat}.each do |name|
+ @recipe.zen_master name do
+ peace true
+ end
+ end
+ @recipe.collection.each_index do |i|
+ case i
+ when 0
+ @recipe.collection[i].name.should eql("monkey")
+ when 1
+ @recipe.collection[i].name.should eql("dog")
+ when 2
+ @recipe.collection[i].name.should eql("cat")
+ end
+ end
+ end
+
+ it "should return the new resource after creating it" do
+ res = @recipe.zen_master "makoto" do
+ peace true
+ end
+ res.resource_name.should eql(:zen_master)
+ res.name.should eql("makoto")
+ end
+
+ it "should handle an instance_eval properly" do
+ code = <<-CODE
+zen_master "gnome" do
+ peace = true
+end
+CODE
+ lambda { @recipe.instance_eval(code) }.should_not raise_error
+ @recipe.resources(:zen_master => "gnome").name.should eql("gnome")
+ end
+
+ it "should execute defined resources" do
+ crow_define = Chef::ResourceDefinition.new
+ crow_define.define :crow, :peace => false, :something => true do
+ zen_master "lao tzu" do
+ peace params[:peace]
+ something params[:something]
+ end
+ end
+ @recipe.definitions[:crow] = crow_define
+ @recipe.crow "mine" do
+ peace true
+ end
+ @recipe.resources(:zen_master => "lao tzu").name.should eql("lao tzu")
+ @recipe.resources(:zen_master => "lao tzu").something.should eql(true)
+ end
+
+ it "should load a resource from a ruby file" do
+ @recipe.from_file(File.join(File.dirname(__FILE__), "..", "data", "recipes", "test.rb"))
+ res = @recipe.resources(:file => "/etc/nsswitch.conf")
+ res.name.should eql("/etc/nsswitch.conf")
+ res.action.should eql(:create)
+ res.owner.should eql("root")
+ res.group.should eql("root")
+ res.mode.should eql(0644)
+ end
+
+ it "should raise an exception if the file cannot be found or read" do
+ lambda { @recipe.from_file("/tmp/monkeydiving") }.should raise_error(IOError)
+ end
+
+ it "should evaluate another recipe with recipe_require" do
+ Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks")
+ @recipe.cookbook_loader.load_cookbooks
+ @recipe.require_recipe "openldap::gigantor"
+ res = @recipe.resources(:cat => "blanket")
+ res.name.should eql("blanket")
+ res.pretty_kitty.should eql(false)
+ end
+
+ it "should load the default recipe for a cookbook if require_recipe is called without a ::" do
+ Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks")
+ @recipe.cookbook_loader.load_cookbooks
+ @recipe.require_recipe "openldap"
+ res = @recipe.resources(:cat => "blanket")
+ res.name.should eql("blanket")
+ res.pretty_kitty.should eql(true)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/bash_spec.rb b/chef/spec/unit/resource/bash_spec.rb
new file mode 100644
index 0000000000..f86e63d32e
--- /dev/null
+++ b/chef/spec/unit/resource/bash_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Bash do
+
+ before(:each) do
+ @resource = Chef::Resource::Bash.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Bash" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Bash)
+ end
+
+ it "should have a resource name of :bash" do
+ @resource.resource_name.should eql(:bash)
+ end
+
+ it "should have an interpreter of bash" do
+ @resource.interpreter.should eql("bash")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/csh_spec.rb b/chef/spec/unit/resource/csh_spec.rb
new file mode 100644
index 0000000000..67ba4499fa
--- /dev/null
+++ b/chef/spec/unit/resource/csh_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Csh do
+
+ before(:each) do
+ @resource = Chef::Resource::Csh.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Csh" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Csh)
+ end
+
+ it "should have a resource name of :csh" do
+ @resource.resource_name.should eql(:csh)
+ end
+
+ it "should have an interpreter of csh" do
+ @resource.interpreter.should eql("csh")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/directory_spec.rb b/chef/spec/unit/resource/directory_spec.rb
new file mode 100644
index 0000000000..98cc28fbc6
--- /dev/null
+++ b/chef/spec/unit/resource/directory_spec.rb
@@ -0,0 +1,79 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Directory do
+
+ before(:each) do
+ @resource = Chef::Resource::Directory.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Directory" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Directory)
+ end
+
+ it "should have a name" do
+ @resource.name.should eql("fakey_fakerton")
+ end
+
+ it "should have a default action of 'create'" do
+ @resource.action.should eql(:create)
+ end
+
+ it "should accept create or delete for action" do
+ lambda { @resource.action "create" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "delete" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "blues" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a group name or id for group" do
+ lambda { @resource.group "root" }.should_not raise_error(ArgumentError)
+ lambda { @resource.group 123 }.should_not raise_error(ArgumentError)
+ lambda { @resource.group "root*goo" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a valid unix file mode" do
+ lambda { @resource.mode 0444 }.should_not raise_error(ArgumentError)
+ lambda { @resource.mode 444 }.should_not raise_error(ArgumentError)
+ lambda { @resource.mode 4 }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a user name or id for owner" do
+ lambda { @resource.owner "root" }.should_not raise_error(ArgumentError)
+ lambda { @resource.owner 123 }.should_not raise_error(ArgumentError)
+ lambda { @resource.owner "root*goo" }.should raise_error(ArgumentError)
+ end
+
+ it "should use the object name as the path by default" do
+ @resource.path.should eql("fakey_fakerton")
+ end
+
+ it "should accept a string as the path" do
+ lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError)
+ lambda { @resource.path Hash.new }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to have specify whether the action is recursive with true/false" do
+ lambda { @resource.recursive true }.should_not raise_error(ArgumentError)
+ lambda { @resource.recursive false }.should_not raise_error(ArgumentError)
+ lambda { @resource.recursive "monkey" }.should raise_error(ArgumentError)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/execute_spec.rb b/chef/spec/unit/resource/execute_spec.rb
new file mode 100644
index 0000000000..94cb3d3d92
--- /dev/null
+++ b/chef/spec/unit/resource/execute_spec.rb
@@ -0,0 +1,102 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Execute do
+
+ before(:each) do
+ @resource = Chef::Resource::Execute.new("some command")
+ end
+
+ it "should create a new Chef::Resource::Execute" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Execute)
+ end
+
+ it "should set the command to the first argument to new" do
+ @resource.command.should eql("some command")
+ end
+
+ it "should accept a string for the command to run" do
+ @resource.command "something"
+ @resource.command.should eql("something")
+ end
+
+ it "should accept a string for the cwd" do
+ @resource.cwd "something"
+ @resource.cwd.should eql("something")
+ end
+
+ it "should accept a hash for the environment" do
+ test_hash = { :one => :two }
+ @resource.environment(test_hash)
+ @resource.environment.should eql(test_hash)
+ end
+
+ it "should accept a string for the group" do
+ @resource.group "something"
+ @resource.group.should eql("something")
+ end
+
+ it "should accept an integer for the group" do
+ @resource.group 1
+ @resource.group.should eql(1)
+ end
+
+ it "should accept a string for onlyif" do
+ @resource.onlyif "woot"
+ @resource.onlyif.should eql("woot")
+ end
+
+ it "should accept an array for the execution path" do
+ @resource.path ["woot"]
+ @resource.path.should eql(["woot"])
+ end
+
+ it "should accept an integer for the return code" do
+ @resource.returns 1
+ @resource.returns.should eql(1)
+ end
+
+ it "should accept an integer for the timeout" do
+ @resource.timeout 1
+ @resource.timeout.should eql(1)
+ end
+
+ it "should accept a string for unless" do
+ @resource.unless "woot"
+ @resource.unless.should eql("woot")
+ end
+
+ it "should accept a string for the user" do
+ @resource.user "something"
+ @resource.user.should eql("something")
+ end
+
+ it "should accept an integer for the user" do
+ @resource.user 1
+ @resource.user.should eql(1)
+ end
+
+ it "should accept a string for creates" do
+ @resource.creates "something"
+ @resource.creates.should eql("something")
+ end
+
+end
diff --git a/chef/spec/unit/resource/file_spec.rb b/chef/spec/unit/resource/file_spec.rb
new file mode 100644
index 0000000000..b67598d738
--- /dev/null
+++ b/chef/spec/unit/resource/file_spec.rb
@@ -0,0 +1,92 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::File do
+
+ before(:each) do
+ @resource = Chef::Resource::File.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::File" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::File)
+ end
+
+ it "should have a name" do
+ @resource.name.should eql("fakey_fakerton")
+ end
+
+ it "should have a default action of 'create'" do
+ @resource.action.should eql("create")
+ end
+
+ it "should be set to back up 5 files by default" do
+ @resource.backup.should eql(5)
+ end
+
+ it "should only accept false or a number for backup" do
+ lambda { @resource.backup true }.should raise_error(ArgumentError)
+ lambda { @resource.backup false }.should_not raise_error(ArgumentError)
+ lambda { @resource.backup 10 }.should_not raise_error(ArgumentError)
+ lambda { @resource.backup "blues" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept an md5sum for checksum" do
+ lambda { @resource.checksum "bfda9e7a13afb123433667c2c7801d11" }.should_not raise_error(ArgumentError)
+ lambda { @resource.checksum "monkey!" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept create, delete or touch for action" do
+ lambda { @resource.action "create" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "delete" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "touch" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "blues" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a group name or id for group" do
+ lambda { @resource.group "root" }.should_not raise_error(ArgumentError)
+ lambda { @resource.group 123 }.should_not raise_error(ArgumentError)
+ lambda { @resource.group "root*goo" }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a valid unix file mode" do
+ lambda { @resource.mode 0444 }.should_not raise_error(ArgumentError)
+ @resource.mode.should eql(0444)
+ lambda { @resource.mode 444 }.should_not raise_error(ArgumentError)
+ lambda { @resource.mode 4 }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a user name or id for owner" do
+ lambda { @resource.owner "root" }.should_not raise_error(ArgumentError)
+ lambda { @resource.owner 123 }.should_not raise_error(ArgumentError)
+ lambda { @resource.owner "root*goo" }.should raise_error(ArgumentError)
+ end
+
+ it "should use the object name as the path by default" do
+ @resource.path.should eql("fakey_fakerton")
+ end
+
+ it "should accept a string as the path" do
+ lambda { @resource.path "/tmp" }.should_not raise_error(ArgumentError)
+ @resource.path.should eql("/tmp")
+ lambda { @resource.path Hash.new }.should raise_error(ArgumentError)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/link_spec.rb b/chef/spec/unit/resource/link_spec.rb
new file mode 100644
index 0000000000..fa0810cced
--- /dev/null
+++ b/chef/spec/unit/resource/link_spec.rb
@@ -0,0 +1,78 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Link do
+
+ before(:each) do
+ @resource = Chef::Resource::Link.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Link" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Link)
+ end
+
+ it "should have a name" do
+ @resource.name.should eql("fakey_fakerton")
+ end
+
+ it "should have a default action of 'create'" do
+ @resource.action.should eql(:create)
+ end
+
+ it "should accept create or delete for action" do
+ lambda { @resource.action "create" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "delete" }.should_not raise_error(ArgumentError)
+ lambda { @resource.action "blues" }.should raise_error(ArgumentError)
+ end
+
+ it "should use the object name as the source_file by default" do
+ @resource.source_file.should eql("fakey_fakerton")
+ end
+
+ it "should accept a string as the source_file" do
+ lambda { @resource.source_file "/tmp" }.should_not raise_error(ArgumentError)
+ lambda { @resource.source_file Hash.new }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to set a target_file" do
+ @resource.target_file "/tmp/foo"
+ @resource.target_file.should eql("/tmp/foo")
+ end
+
+ it "should allow you to specify the link type" do
+ @resource.link_type "symbolic"
+ @resource.link_type.should eql(:symbolic)
+ end
+
+ it "should default to a symbolic link" do
+ @resource.link_type.should eql(:symbolic)
+ end
+
+ it "should accept a hard link_type" do
+ @resource.link_type :hard
+ @resource.link_type.should eql(:hard)
+ end
+
+ it "should reject any other link_type but :hard and :symbolic" do
+ lambda { @resource.link_type "x-men" }.should raise_error(ArgumentError)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/package_spec.rb b/chef/spec/unit/resource/package_spec.rb
new file mode 100644
index 0000000000..8756e4f603
--- /dev/null
+++ b/chef/spec/unit/resource/package_spec.rb
@@ -0,0 +1,56 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Package do
+
+ before(:each) do
+ @resource = Chef::Resource::Package.new("emacs")
+ end
+
+ it "should create a new Chef::Resource::Package" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Package)
+ end
+
+ it "should set the package_name to the first argument to new" do
+ @resource.package_name.should eql("emacs")
+ end
+
+ it "should accept a string for the package name" do
+ @resource.package_name "something"
+ @resource.package_name.should eql("something")
+ end
+
+ it "should accept a string for the version" do
+ @resource.version "something"
+ @resource.version.should eql("something")
+ end
+
+ it "should accept a string for the response file" do
+ @resource.response_file "something"
+ @resource.response_file.should eql("something")
+ end
+
+ it "should accept a string for the source" do
+ @resource.source "something"
+ @resource.source.should eql("something")
+ end
+
+end
diff --git a/chef/spec/unit/resource/perl_spec.rb b/chef/spec/unit/resource/perl_spec.rb
new file mode 100644
index 0000000000..528bcef08c
--- /dev/null
+++ b/chef/spec/unit/resource/perl_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Perl do
+
+ before(:each) do
+ @resource = Chef::Resource::Perl.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Perl" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Perl)
+ end
+
+ it "should have a resource name of :perl" do
+ @resource.resource_name.should eql(:perl)
+ end
+
+ it "should have an interpreter of perl" do
+ @resource.interpreter.should eql("perl")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/python_spec.rb b/chef/spec/unit/resource/python_spec.rb
new file mode 100644
index 0000000000..11c5b84d04
--- /dev/null
+++ b/chef/spec/unit/resource/python_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Python do
+
+ before(:each) do
+ @resource = Chef::Resource::Python.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Python" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Python)
+ end
+
+ it "should have a resource name of :python" do
+ @resource.resource_name.should eql(:python)
+ end
+
+ it "should have an interpreter of python" do
+ @resource.interpreter.should eql("python")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/remote_directory_spec.rb b/chef/spec/unit/resource/remote_directory_spec.rb
new file mode 100644
index 0000000000..2d9ce122af
--- /dev/null
+++ b/chef/spec/unit/resource/remote_directory_spec.rb
@@ -0,0 +1,71 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::RemoteDirectory do
+
+ before(:each) do
+ @resource = Chef::Resource::RemoteDirectory.new("/etc/dunk")
+ end
+
+ it "should create a new Chef::Resource::RemoteDirectory" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::RemoteDirectory)
+ end
+
+ it "should set the path to the first argument to new" do
+ @resource.path.should eql("/etc/dunk")
+ end
+
+ it "should accept a string for the remote directory source" do
+ @resource.source "foo"
+ @resource.source.should eql("foo")
+ end
+
+ it "should accept a number for the remote files backup" do
+ @resource.files_backup 1
+ @resource.files_backup.should eql(1)
+ end
+
+ it "should accept false for the remote files backup" do
+ @resource.files_backup false
+ @resource.files_backup.should eql(false)
+ end
+
+ it "should accept 3 or 4 digets for the files_mode" do
+ @resource.files_mode 100
+ @resource.files_mode.should eql(100)
+ @resource.files_mode 1000
+ @resource.files_mode.should eql(1000)
+ end
+
+ it "should accept a string or number for the files group" do
+ @resource.files_group "heart"
+ @resource.files_group.should eql("heart")
+ @resource.files_group 1000
+ @resource.files_group.should eql(1000)
+ end
+
+ it "should accept a string or number for the files owner" do
+ @resource.files_owner "heart"
+ @resource.files_owner.should eql("heart")
+ @resource.files_owner 1000
+ @resource.files_owner.should eql(1000)
+ end
+end
diff --git a/chef/spec/unit/resource/remote_file_spec.rb b/chef/spec/unit/resource/remote_file_spec.rb
new file mode 100644
index 0000000000..4c052e0260
--- /dev/null
+++ b/chef/spec/unit/resource/remote_file_spec.rb
@@ -0,0 +1,38 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::RemoteFile do
+
+ before(:each) do
+ @resource = Chef::Resource::RemoteFile.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::RemoteFile" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::File)
+ @resource.should be_a_kind_of(Chef::Resource::RemoteFile)
+ end
+
+ it "should accept a string for the remote file source" do
+ @resource.source "something"
+ @resource.source.should eql("something")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/ruby_spec.rb b/chef/spec/unit/resource/ruby_spec.rb
new file mode 100644
index 0000000000..5351462154
--- /dev/null
+++ b/chef/spec/unit/resource/ruby_spec.rb
@@ -0,0 +1,40 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Ruby do
+
+ before(:each) do
+ @resource = Chef::Resource::Ruby.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Ruby" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Ruby)
+ end
+
+ it "should have a resource name of :ruby" do
+ @resource.resource_name.should eql(:ruby)
+ end
+
+ it "should have an interpreter of ruby" do
+ @resource.interpreter.should eql("ruby")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/script_spec.rb b/chef/spec/unit/resource/script_spec.rb
new file mode 100644
index 0000000000..f003028fd6
--- /dev/null
+++ b/chef/spec/unit/resource/script_spec.rb
@@ -0,0 +1,50 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Script do
+
+ before(:each) do
+ @resource = Chef::Resource::Script.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Script" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Script)
+ end
+
+ it "should have a resource name of :script" do
+ @resource.resource_name.should eql(:script)
+ end
+
+ it "should set command to the argument provided to new" do
+ @resource.command.should eql("fakey_fakerton")
+ end
+
+ it "should accept a string for the code" do
+ @resource.code "hey jude"
+ @resource.code.should eql("hey jude")
+ end
+
+ it "should accept a string for the interpreter" do
+ @resource.interpreter "naaaaNaNaNaaNaaNaaNaa"
+ @resource.interpreter.should eql("naaaaNaNaNaaNaaNaaNaa")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource/service_spec.rb b/chef/spec/unit/resource/service_spec.rb
new file mode 100644
index 0000000000..f5941f89ae
--- /dev/null
+++ b/chef/spec/unit/resource/service_spec.rb
@@ -0,0 +1,100 @@
+#
+# Author:: AJ Christensen (<aj@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Service do
+
+ before(:each) do
+ @resource = Chef::Resource::Service.new("chef")
+ end
+
+ it "should create a new Chef::Resource::Service" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::Service)
+ end
+
+ it "should set the service_name to the first argument to new" do
+ @resource.service_name.should eql("chef")
+ end
+
+ it "should set the pattern to be the service name by default" do
+ @resource.pattern.should eql("chef")
+ end
+
+ it "should accept a string for the service name" do
+ @resource.service_name "something"
+ @resource.service_name.should eql("something")
+ end
+
+ it "should accept a string for the service pattern" do
+ @resource.pattern ".*"
+ @resource.pattern.should eql(".*")
+ end
+
+ it "should not accept a regexp for the service pattern" do
+ lambda {
+ @resource.pattern /.*/
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a string for the service start command" do
+ @resource.start_command "/etc/init.d/chef start"
+ @resource.start_command.should eql("/etc/init.d/chef start")
+ end
+
+ it "should not accept a regexp for the service start command" do
+ lambda {
+ @resource.start_command /.*/
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a string for the service stop command" do
+ @resource.stop_command "/etc/init.d/chef stop"
+ @resource.stop_command.should eql("/etc/init.d/chef stop")
+ end
+
+ it "should not accept a regexp for the service stop command" do
+ lambda {
+ @resource.stop_command /.*/
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a string for the service status command" do
+ @resource.status_command "/etc/init.d/chef status"
+ @resource.status_command.should eql("/etc/init.d/chef status")
+ end
+
+ it "should not accept a regexp for the service status command" do
+ lambda {
+ @resource.status_command /.*/
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should accept a string for the service restart command" do
+ @resource.restart_command "/etc/init.d/chef restart"
+ @resource.restart_command.should eql("/etc/init.d/chef restart")
+ end
+
+ it "should not accept a regexp for the service restart command" do
+ lambda {
+ @resource.restart_command /.*/
+ }.should raise_error(ArgumentError)
+ end
+
+end
diff --git a/chef/spec/unit/resource/template_spec.rb b/chef/spec/unit/resource/template_spec.rb
new file mode 100644
index 0000000000..106fe77bda
--- /dev/null
+++ b/chef/spec/unit/resource/template_spec.rb
@@ -0,0 +1,43 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
+
+describe Chef::Resource::Template do
+
+ before(:each) do
+ @resource = Chef::Resource::Template.new("fakey_fakerton")
+ end
+
+ it "should create a new Chef::Resource::Template" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ @resource.should be_a_kind_of(Chef::Resource::File)
+ @resource.should be_a_kind_of(Chef::Resource::Template)
+ end
+
+ it "should accept a string for the template source" do
+ @resource.source "something"
+ @resource.source.should eql("something")
+ end
+
+ it "should accept a hash for the variable list" do
+ @resource.variables({ :reluctance => :awkward })
+ @resource.variables.should == { :reluctance => :awkward }
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource_collection_spec.rb b/chef/spec/unit/resource_collection_spec.rb
new file mode 100644
index 0000000000..37eeac18db
--- /dev/null
+++ b/chef/spec/unit/resource_collection_spec.rb
@@ -0,0 +1,201 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::ResourceCollection do
+
+ before(:each) do
+ @rc = Chef::ResourceCollection.new()
+ @resource = Chef::Resource::ZenMaster.new("makoto")
+ end
+
+ it "should return a Chef::ResourceCollection" do
+ @rc.should be_kind_of(Chef::ResourceCollection)
+ end
+
+ it "should accept Chef::Resources through [index]" do
+ lambda { @rc[0] = @resource }.should_not raise_error
+ lambda { @rc[0] = "string" }.should raise_error
+ end
+
+ it "should not accept duplicate resources [index]=" do
+ @rc[0] = @resource
+ lambda { @rc[1] = @resource }.should raise_error(ArgumentError)
+ end
+
+ it "should accept Chef::Resources through pushing" do
+ lambda { @rc.push(@resource) }.should_not raise_error
+ lambda { @rc.push("string") }.should raise_error
+ end
+
+ it "should not accept duplicate resources through pushing" do
+ lambda { @rc.push(@resource) }.should_not raise_error
+ lambda { @rc.push(@resource) }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to fetch Chef::Resources by position" do
+ @rc[0] = @resource
+ @rc[0].should eql(@resource)
+ end
+
+ it "should accept the << operator" do
+ lambda { @rc << @resource }.should_not raise_error
+ end
+
+ it "should not accept duplicate resources through the << operator" do
+ lambda { @rc << @resource }.should_not raise_error
+ lambda { @rc << @resource }.should raise_error(ArgumentError)
+ end
+
+ it "should allow you to iterate over every resource in the collection" do
+ load_up_resources
+ results = Array.new
+ lambda {
+ @rc.each do |r|
+ results << r.name
+ end
+ }.should_not raise_error
+ results.each_index do |i|
+ case i
+ when 0
+ results[i].should eql("dog")
+ when 1
+ results[i].should eql("cat")
+ when 2
+ results[i].should eql("monkey")
+ end
+ end
+ end
+
+ it "should allow you to iterate over every resource by index" do
+ load_up_resources
+ results = Array.new
+ lambda {
+ @rc.each_index do |i|
+ results << @rc[i].name
+ end
+ }.should_not raise_error()
+ results.each_index do |i|
+ case i
+ when 0
+ results[i].should eql("dog")
+ when 1
+ results[i].should eql("cat")
+ when 2
+ results[i].should eql("monkey")
+ end
+ end
+ end
+
+ it "should allow you to find resources by name via lookup" do
+ zmr = Chef::Resource::ZenMaster.new("dog")
+ @rc << zmr
+ @rc.lookup(zmr.to_s).should eql(zmr)
+
+ zmr = Chef::Resource::ZenMaster.new("cat")
+ @rc[0] = zmr
+ @rc.lookup(zmr).should eql(zmr)
+
+ zmr = Chef::Resource::ZenMaster.new("monkey")
+ @rc.push(zmr)
+ @rc.lookup(zmr).should eql(zmr)
+ end
+
+ it "should raise an exception if you send something strange to lookup" do
+ lambda { @rc.lookup(:symbol) }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an exception if it cannot find a resource with lookup" do
+ lambda { @rc.lookup("zen_master[dog]") }.should raise_error(ArgumentError)
+ end
+
+ it "should find a resource by symbol and name (:zen_master => monkey)" do
+ load_up_resources
+ @rc.resources(:zen_master => "monkey").name.should eql("monkey")
+ end
+
+ it "should find a resource by symbol and array of names (:zen_master => [a,b])" do
+ load_up_resources
+ results = @rc.resources(:zen_master => [ "monkey", "dog" ])
+ results.length.should eql(2)
+ check_by_names(results, "monkey", "dog")
+ end
+
+ it "should find resources of multiple kinds (:zen_master => a, :file => b)" do
+ load_up_resources
+ results = @rc.resources(:zen_master => "monkey", :file => "something")
+ results.length.should eql(2)
+ check_by_names(results, "monkey", "something")
+ end
+
+ it "should find a resource by string zen_master[a]" do
+ load_up_resources
+ @rc.resources("zen_master[monkey]").name.should eql("monkey")
+ end
+
+ it "should find resources by strings of zen_master[a,b]" do
+ load_up_resources
+ results = @rc.resources("zen_master[monkey,dog]")
+ results.length.should eql(2)
+ check_by_names(results, "monkey", "dog")
+ end
+
+ it "should find resources of multiple types by strings of zen_master[a]" do
+ load_up_resources
+ results = @rc.resources("zen_master[monkey]", "file[something]")
+ results.length.should eql(2)
+ check_by_names(results, "monkey", "something")
+ end
+
+ it "should raise an exception if you pass a bad name to resources" do
+ lambda { @rc.resources("michael jackson") }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an exception if you pass something other than a string or hash to resource" do
+ lambda { @rc.resources([Array.new]) }.should raise_error(ArgumentError)
+ end
+
+ it "should serialize to json" do
+ json = @rc.to_json
+ json.should =~ /json_class/
+ json.should =~ /instance_vars/
+ end
+
+ it "should deserialize itself from json" do
+ @rc << @resource
+ json = @rc.to_json
+ s_rc = JSON.parse(json)
+ s_rc.should be_a_kind_of(Chef::ResourceCollection)
+ s_rc[0].name.should eql(@resource.name)
+ end
+
+ def check_by_names(results, *names)
+ names.each do |res_name|
+ results.detect{ |res| res.name == res_name }.should_not eql(nil)
+ end
+ end
+
+ def load_up_resources
+ %w{dog cat monkey}.each do |n|
+ @rc << Chef::Resource::ZenMaster.new(n)
+ end
+ @rc << Chef::Resource::File.new("something")
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource_definition_spec.rb b/chef/spec/unit/resource_definition_spec.rb
new file mode 100644
index 0000000000..8ba7556b70
--- /dev/null
+++ b/chef/spec/unit/resource_definition_spec.rb
@@ -0,0 +1,83 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::ResourceDefinition do
+ before(:each) do
+ @def = Chef::ResourceDefinition.new()
+ end
+
+ it "should accept a new definition with a symbol for a name" do
+ lambda {
+ @def.define :smoke do
+ end
+ }.should_not raise_error(ArgumentError)
+ lambda {
+ @def.define "george washington" do
+ end
+ }.should raise_error(ArgumentError)
+ @def.name.should eql(:smoke)
+ end
+
+ it "should accept a new definition with a hash" do
+ lambda {
+ @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do
+ end
+ }.should_not raise_error(ArgumentError)
+ end
+
+ it "should expose the prototype hash params in the params hash" do
+ @def.define :smoke, :cigar => "cuban", :cigarette => "marlboro" do
+ end
+ @def.params[:cigar].should eql("cuban")
+ @def.params[:cigarette].should eql("marlboro")
+ end
+
+ it "should store the block passed to define as a proc under recipe" do
+ @def.define :smoke do
+ "I am what I am"
+ end
+ @def.recipe.should be_a_kind_of(Proc)
+ @def.recipe.call.should eql("I am what I am")
+ end
+
+ it "should set paramaters based on method_missing" do
+ @def.mind "to fly"
+ @def.params[:mind].should eql("to fly")
+ end
+
+ it "should raise an exception if prototype_params is not a hash" do
+ lambda {
+ @def.define :monkey, Array.new do
+ end
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an exception if define is called without a block" do
+ lambda {
+ @def.define :monkey
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should load a description from a file" do
+ @def.from_file(File.join(File.dirname(__FILE__), "..", "data", "definitions", "test.rb"))
+ @def.name.should eql(:rico_suave)
+ @def.params[:rich].should eql("smooth")
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/resource_spec.rb b/chef/spec/unit/resource_spec.rb
new file mode 100644
index 0000000000..3ca48060fa
--- /dev/null
+++ b/chef/spec/unit/resource_spec.rb
@@ -0,0 +1,144 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Resource do
+ before(:each) do
+ @resource = Chef::Resource.new("funk")
+ end
+
+ it "should create a new Chef::Resource" do
+ @resource.should be_a_kind_of(Chef::Resource)
+ end
+
+ it "should have a name" do
+ @resource.name.should eql("funk")
+ end
+
+ it "should let you set a new name" do
+ @resource.name "monkey"
+ @resource.name.should eql("monkey")
+ end
+
+ it "should not be valid without a name" do
+ lambda { @resource.name false }.should raise_error(ArgumentError)
+ end
+
+ it "should always have a string for name" do
+ lambda { @resource.name Hash.new }.should raise_error(ArgumentError)
+ end
+
+ it "should accept true or false for noop" do
+ lambda { @resource.noop true }.should_not raise_error(ArgumentError)
+ lambda { @resource.noop false }.should_not raise_error(ArgumentError)
+ lambda { @resource.noop "eat it" }.should raise_error(ArgumentError)
+ end
+
+ it "should make notified resources appear in the actions hash" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ @resource.notifies :reload, @resource.resources(:zen_master => "coffee")
+ @resource.actions[:reload][:delayed][0].name.should eql("coffee")
+ end
+
+ it "should make notified resources be capable of acting immediately" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ @resource.notifies :reload, @resource.resources(:zen_master => "coffee"), :immediate
+ @resource.actions[:reload][:immediate][0].name.should eql("coffee")
+ end
+
+ it "should raise an exception if told to act in other than :delay or :immediate(ly)" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ lambda {
+ @resource.notifies :reload, @resource.resources(:zen_master => "coffee"), :someday
+ }.should raise_error(ArgumentError)
+ end
+
+ it "should allow multiple notified resources appear in the actions hash" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ @resource.notifies :reload, @resource.resources(:zen_master => "coffee")
+ @resource.actions[:reload][:delayed][0].name.should eql("coffee")
+ @resource.collection << Chef::Resource::ZenMaster.new("beans")
+ @resource.notifies :reload, @resource.resources(:zen_master => "beans")
+ @resource.actions[:reload][:delayed][1].name.should eql("beans")
+ end
+
+ it "should make resources appear in the actions hash of subscribed nodes" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ zr = @resource.resources(:zen_master => "coffee")
+ @resource.subscribes :reload, zr
+ zr.actions[:reload][:delayed][0].name.should eql("funk")
+ end
+
+ it "should make resources appear in the actions hash of subscribed nodes" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ zr = @resource.resources(:zen_master => "coffee")
+ @resource.subscribes :reload, zr
+ zr.actions[:reload][:delayed][0].name.should eql("funk")
+
+ @resource.collection << Chef::Resource::ZenMaster.new("bean")
+ zrb = @resource.resources(:zen_master => "bean")
+ zrb.subscribes :reload, zr
+ zr.actions[:reload][:delayed][1].name.should eql("bean")
+ end
+
+ it "should make subscribed resources be capable of acting immediately" do
+ @resource.collection << Chef::Resource::ZenMaster.new("coffee")
+ zr = @resource.resources(:zen_master => "coffee")
+ @resource.subscribes :reload, zr, :immediately
+ zr.actions[:reload][:immediate][0].name.should eql("funk")
+ end
+
+ it "should return a value if not defined" do
+ zm = Chef::Resource::ZenMaster.new("coffee")
+ zm.something(true).should eql(true)
+ zm.something.should eql(true)
+ zm.something(false).should eql(false)
+ zm.something.should eql(false)
+ end
+
+ it "should become a string like resource_name[name]" do
+ zm = Chef::Resource::ZenMaster.new("coffee")
+ zm.to_s.should eql("zen_master[coffee]")
+ end
+
+ it "should return the arguments passed with 'is'" do
+ zm = Chef::Resource::ZenMaster.new("coffee")
+ res = zm.is("one", "two", "three")
+ res.should eql([ "one", "two", "three" ])
+ end
+
+ it "should allow arguments preceeded by is to methods" do
+ @resource.noop(@resource.is(true))
+ @resource.noop.should eql(true)
+ end
+
+ it "should serialize to json" do
+ json = @resource.to_json
+ json.should =~ /json_class/
+ json.should =~ /instance_vars/
+ end
+
+ it "should deserialize itself from json" do
+ json = @resource.to_json
+ serialized_node = JSON.parse(json)
+ serialized_node.should be_a_kind_of(Chef::Resource)
+ serialized_node.name.should eql(@resource.name)
+ end
+
+end \ No newline at end of file
diff --git a/chef/spec/unit/rest_spec.rb b/chef/spec/unit/rest_spec.rb
new file mode 100644
index 0000000000..56c2954a1a
--- /dev/null
+++ b/chef/spec/unit/rest_spec.rb
@@ -0,0 +1,228 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+require 'uri'
+require 'net/https'
+
+describe Chef::REST, "initialize method" do
+ it "should create a new Chef::REST" do
+ Chef::REST.new("url").should be_kind_of(Chef::REST)
+ end
+end
+
+describe Chef::REST, "get_rest method" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ r = Chef::REST.new("url")
+ r.stub!(:run_request)
+ r.get_rest("monkey")
+ end
+
+ it "should call run_request :GET with the composed url object" do
+ URI.stub!(:parse).and_return(true)
+ r = Chef::REST.new("url")
+ r.should_receive(:run_request).with(:GET, true, false, 10, false).and_return(true)
+ r.get_rest("monkey")
+ end
+end
+
+describe Chef::REST, "delete_rest method" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ r = Chef::REST.new("url")
+ r.stub!(:run_request)
+ r.delete_rest("monkey")
+ end
+
+ it "should call run_request :DELETE with the composed url object" do
+ URI.stub!(:parse).and_return(true)
+ r = Chef::REST.new("url")
+ r.should_receive(:run_request).with(:DELETE, true).and_return(true)
+ r.delete_rest("monkey")
+ end
+end
+
+describe Chef::REST, "post_rest method" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ r = Chef::REST.new("url")
+ r.stub!(:run_request)
+ r.post_rest("monkey", "data")
+ end
+
+ it "should call run_request :POST with the composed url object and data" do
+ URI.stub!(:parse).and_return(true)
+ r = Chef::REST.new("url")
+ r.should_receive(:run_request).with(:POST, true, "data").and_return(true)
+ r.post_rest("monkey", "data")
+ end
+end
+
+describe Chef::REST, "put_rest method" do
+ it "should create a url from the path and base url" do
+ URI.should_receive(:parse).with("url/monkey")
+ r = Chef::REST.new("url")
+ r.stub!(:run_request)
+ r.put_rest("monkey", "data")
+ end
+
+ it "should call run_request :PUT with the composed url object and data" do
+ URI.stub!(:parse).and_return(true)
+ r = Chef::REST.new("url")
+ r.should_receive(:run_request).with(:PUT, true, "data").and_return(true)
+ r.put_rest("monkey", "data")
+ end
+end
+
+describe Chef::REST, "run_request method" do
+ before(:each) do
+ @r = Chef::REST.new("url")
+ @url_mock = mock("URI", :null_object => true)
+ @url_mock.stub!(:host).and_return("one")
+ @url_mock.stub!(:port).and_return("80")
+ @url_mock.stub!(:path).and_return("/")
+ @url_mock.stub!(:query).and_return("foo=bar")
+ @url_mock.stub!(:scheme).and_return("https")
+ @url_mock.stub!(:to_s).and_return("https://one:80/?foo=bar")
+ @http_response_mock = mock("Net::HTTPSuccess", :null_object => true)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(true)
+ @http_response_mock.stub!(:body).and_return("ninja")
+ @http_mock = mock("Net::HTTP", :null_object => true)
+ @http_mock.stub!(:verify_mode=).and_return(true)
+ @http_mock.stub!(:read_timeout=).and_return(true)
+ @http_mock.stub!(:use_ssl=).with(true).and_return(true)
+ @data_mock = mock("Data", :null_object => true)
+ @data_mock.stub!(:to_json).and_return('{ "one": "two" }')
+ @request_mock = mock("Request", :null_object => true)
+ @request_mock.stub!(:body=).and_return(true)
+ @request_mock.stub!(:method).and_return(true)
+ @request_mock.stub!(:path).and_return(true)
+ @http_mock.stub!(:request).and_return(@http_response_mock)
+ @tf_mock = mock(Tempfile, { :print => true, :close => true })
+ Tempfile.stub!(:new).with("chef-rest").and_return(@tf_mock)
+ end
+
+ def do_run_request(method=:GET, data=false, limit=10, raw=false)
+ Net::HTTP.stub!(:new).and_return(@http_mock)
+ @r.run_request(method, @url_mock, data, limit, raw)
+ end
+
+ it "should raise an exception if the redirect limit is 0" do
+ lambda { @r.run_request(:GET, "/", false, 0)}.should raise_error(ArgumentError)
+ end
+
+ it "should use SSL if the url starts with https" do
+ @url_mock.should_receive(:scheme).and_return("https")
+ @http_mock.should_receive(:use_ssl=).with(true).and_return(true)
+ do_run_request
+ end
+
+ it "should set the OpenSSL Verify Mode to verify_none if requested" do
+ @http_mock.should_receive(:verify_mode=).and_return(true)
+ do_run_request
+ end
+
+ it "should set a read timeout based on the rest_timeout config option" do
+ Chef::Config[:rest_timeout] = 10
+ @http_mock.should_receive(:read_timeout=).with(10).and_return(true)
+ do_run_request
+ end
+
+ it "should build a new HTTP GET request" do
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request
+ end
+
+ it "should build a new HTTP POST request" do
+ Net::HTTP::Post.should_receive(:new).with("/",
+ { 'Accept' => 'application/json', "Content-Type" => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request(:POST, @data_mock)
+ end
+
+ it "should build a new HTTP PUT request" do
+ Net::HTTP::Put.should_receive(:new).with("/",
+ { 'Accept' => 'application/json', "Content-Type" => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request(:PUT, @data_mock)
+ end
+
+ it "should build a new HTTP DELETE request" do
+ Net::HTTP::Delete.should_receive(:new).with("/?foo=bar",
+ { 'Accept' => 'application/json' }
+ ).and_return(@request_mock)
+ do_run_request(:DELETE)
+ end
+
+ it "should raise an error if the method is not GET/PUT/POST/DELETE" do
+ lambda { do_run_request(:MONKEY) }.should raise_error(ArgumentError)
+ end
+
+ it "should run an http request" do
+ @http_mock.should_receive(:request).and_return(@http_response_mock)
+ do_run_request
+ end
+
+ it "should return the body of the response on success" do
+ do_run_request.should eql("ninja")
+ end
+
+ it "should inflate the body as to an object if JSON is returned" do
+ @http_response_mock.stub!(:[]).with('content-type').and_return("application/json")
+ JSON.should_receive(:parse).with("ninja").and_return(true)
+ do_run_request
+ end
+
+ it "should call run_request again on a Redirect response" do
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(true)
+ @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path)
+ lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError)
+ end
+
+ it "should raise an exception on an unsuccessful request" do
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false)
+ @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false)
+ @http_response_mock.should_receive(:error!)
+ do_run_request
+ end
+
+ it "should build a new HTTP GET request without the application/json accept header for raw reqs" do
+ Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {}).and_return(@request_mock)
+ do_run_request(:GET, false, 10, true)
+ end
+
+ it "should create a tempfile for the output of a raw request" do
+ Tempfile.should_receive(:new).with("chef-rest").and_return(@tf_mock)
+ do_run_request(:GET, false, 10, true).should eql(@tf_mock)
+ end
+
+ it "should populate the tempfile with the value of the raw request" do
+ @tf_mock.should_receive(:print, "ninja").once.and_return(true)
+ do_run_request(:GET, false, 10, true)
+ end
+
+ it "should close the tempfile if we're doing a raw request" do
+ @tf_mock.should_receive(:close).once.and_return(true)
+ do_run_request(:GET, false, 10, true)
+ end
+
+end
diff --git a/chef/spec/unit/runner_spec.rb b/chef/spec/unit/runner_spec.rb
new file mode 100644
index 0000000000..f73d5efb22
--- /dev/null
+++ b/chef/spec/unit/runner_spec.rb
@@ -0,0 +1,103 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Runner do
+ before(:each) do
+ @mock_node = mock("Node", :null_object => true)
+ @mock_collection = mock("Resource Collection", :null_object => true)
+ @mock_provider = mock("Provider", :null_object => true)
+ @mock_resource = mock("Resource", :null_object => true)
+ new_runner
+ end
+
+ it "should require a Node and a ResourceCollection" do
+ @mock_node.should_receive(:kind_of?).once.and_return(true)
+ @mock_collection.should_receive(:kind_of?).once.and_return(true)
+ runner = Chef::Runner.new(@mock_node, @mock_collection)
+ runner.should be_a_kind_of(Chef::Runner)
+ end
+
+ it "should raise an exception if you pass the wrong kind of object to new" do
+ @mock_node.stub!(:kind_of?).and_return(false)
+ @mock_collecton.stub!(:kind_of?).and_return(false)
+ lambda { Chef::Runner.new(@mock_node, @mock_collection) }.should raise_error(ArgumentError)
+ end
+
+ it "should pass each resource in the collection to a provider" do
+ @collection.should_receive(:each).once
+ @runner.converge
+ end
+
+ it "should use the provider specified by the resource (if it has one)" do
+ provider = Chef::Provider::Easy.new(@node, @collection[0])
+ @collection[0].should_receive(:provider).once.and_return(Chef::Provider::Easy)
+ Chef::Provider::Easy.should_receive(:new).once.and_return(provider)
+ @runner.converge
+ end
+
+ it "should use the platform provider if it has one" do
+ Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil)
+ @runner.converge
+ end
+
+ it "should run the action for each resource" do
+ Chef::Platform.should_receive(:find_provider_for_node).once.and_return(Chef::Provider::SnakeOil)
+ provider = Chef::Provider::SnakeOil.new(@node, @collection[0])
+ provider.should_receive(:action_sell).once.and_return(true)
+ Chef::Provider::SnakeOil.should_receive(:new).once.and_return(provider)
+ @runner.converge
+ end
+
+ it "should execute immediate actions on changed resources" do
+ Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil)
+ provider = Chef::Provider::SnakeOil.new(@node, @collection[0])
+ Chef::Provider::SnakeOil.should_receive(:new).exactly(3).times.and_return(provider)
+ @collection << Chef::Resource::Cat.new("peanut", @collection)
+ @collection[1].notifies :buy, @collection[0], :immediately
+ @collection[1].updated = true
+ provider.should_receive(:action_buy).once.and_return(true)
+ @runner.converge
+ end
+
+ it "should execute delayed actions on changed resources" do
+ Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil)
+ provider = Chef::Provider::SnakeOil.new(@node, @collection[0])
+ Chef::Provider::SnakeOil.should_receive(:new).exactly(3).times.and_return(provider)
+ @collection << Chef::Resource::Cat.new("peanut", @collection)
+ @collection[1].notifies :buy, @collection[0], :delayed
+ @collection[1].updated = true
+ provider.should_receive(:action_buy).once.and_return(true)
+ @runner.converge
+ end
+
+ def new_runner
+ @node = Chef::Node.new
+ @node.name "latte"
+ @node.operatingsystem "mac_os_x"
+ @node.operatingsystemversion "10.5.1"
+ @collection = Chef::ResourceCollection.new()
+ @collection << Chef::Resource::Cat.new("loulou", @collection)
+ Chef::Platform.set(
+ :resource => :cat,
+ :provider => Chef::Provider::SnakeOil
+ )
+ @runner = Chef::Runner.new(@node, @collection)
+ end
+end \ No newline at end of file
diff --git a/chef/spec/unit/search_index_spec.rb b/chef/spec/unit/search_index_spec.rb
new file mode 100644
index 0000000000..ff234c6a41
--- /dev/null
+++ b/chef/spec/unit/search_index_spec.rb
@@ -0,0 +1,136 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::SearchIndex, "initialize method" do
+ it "should create a new Chef::SearchIndex object" do
+ mf = mock("Ferret::Index::Index", :null_object => true)
+ Ferret::Index::Index.stub!(:new).and_return(mf)
+ Chef::SearchIndex.new.should be_kind_of(Chef::SearchIndex)
+ end
+
+ it "should create a Ferret Indexer" do
+ mf = mock("Ferret::Index::Index", :null_object => true)
+ Ferret::Index::Index.should_receive(:new).and_return(mf)
+ Chef::SearchIndex.new
+ end
+end
+
+describe Chef::SearchIndex, "create_index_object method" do
+ before(:each) do
+ @mf = mock("Ferret::Index::Index", :null_object => true)
+ @fakeobj = mock("ToIndex", :null_object => true)
+ @the_pigeon = { :index_name => "bird", :id => "pigeon" }
+ @fakeobj.stub!(:respond_to?).with(:to_index).and_return(true)
+ @fakeobj.stub!(:to_index).and_return(@the_pigeon)
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ end
+
+ def do_create_index_object
+ index = Chef::SearchIndex.new
+ index.create_index_object(@fakeobj)
+ end
+
+ it "should call to_index if the passed object responds to it" do
+ @fakeobj.should_receive(:respond_to?).with(:to_index).and_return(true)
+ @fakeobj.should_receive(:to_index).and_return(@the_pigeon)
+ do_create_index_object
+ end
+
+ it "should use a hash if the passed argument does not have to_index (but is a hash)" do
+ @fakeobj.stub!(:respond_to?).with(:to_index).and_return(false)
+ @fakeobj.should_receive(:kind_of?).with(Hash).and_return(true)
+ do_create_index_object
+ end
+
+ it "should raise SearchIndex exception if the hash does not contain an :id field" do
+ @the_pigeon.delete(:id)
+ lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex)
+ end
+
+ it "should raise SearchIndex exception if the hash does not contain an :index_name field" do
+ @the_pigeon.delete(:index_name)
+ lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex)
+ end
+
+ it "should raise SearchIndex exception if the hash new_object cannot be indexed" do
+ @fakeobj.stub!(:respond_to?).and_return(false)
+ @fakeobj.stub!(:kind_of?).and_return(false)
+ lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex)
+ end
+
+ it "should turn index hash keys in to symbols if it has strings" do
+ @the_pigeon["john"] = "and_yoko"
+ do_create_index_object.should have_key(:john)
+ end
+end
+
+describe Chef::SearchIndex, "add method" do
+ before(:each) do
+ @mf = mock("Ferret::Index::Index", :null_object => true)
+ @fakeobj = mock("ToIndex", :null_object => true)
+ @the_pigeon = { :index_name => "bird", :id => "pigeon" }
+ @fakeobj.stub!(:respond_to?).with(:to_index).and_return(true)
+ @fakeobj.stub!(:to_index).and_return(@the_pigeon)
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ end
+
+ def do_add
+ index = Chef::SearchIndex.new
+ index.add(@fakeobj)
+ end
+
+ it "should send the resulting hash to the index" do
+ @mf.should_receive(:add_document).with(@the_pigeon)
+ do_add
+ end
+end
+
+describe Chef::SearchIndex, "delete method" do
+ before(:each) do
+ @mf = mock("Ferret::Index::Index", :null_object => true)
+ @fakeobj = mock("ToIndex", :null_object => true)
+ @the_pigeon = { :index_name => "bird", :id => "pigeon" }
+ @fakeobj.stub!(:respond_to?).with(:to_index).and_return(true)
+ @fakeobj.stub!(:to_index).and_return(@the_pigeon)
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ end
+
+ def do_delete(object)
+ index = Chef::SearchIndex.new
+ index.delete(object)
+ end
+
+ it "should delete the resulting hash to the index" do
+ @mf.should_receive(:delete).with(@the_pigeon[:id])
+ do_delete(@fakeobj)
+ end
+end
+
+describe Chef::SearchIndex, "commit method" do
+ before(:each) do
+ @mf = mock("Ferret::Index::Index", :null_object => true)
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ end
+
+ it "should commit index to disk" do
+ @mf.should_receive(:commit)
+ Chef::SearchIndex.new.commit
+ end
+end
diff --git a/chef/spec/unit/search_spec.rb b/chef/spec/unit/search_spec.rb
new file mode 100644
index 0000000000..94e3560a23
--- /dev/null
+++ b/chef/spec/unit/search_spec.rb
@@ -0,0 +1,74 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+
+describe Chef::Search, "initialize method" do
+ before(:each) do
+ @mf = mock("Ferret::Index::Index", :null_object => true)
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ end
+
+ it "should build a Chef::Search object" do
+ Chef::Search.new.should be_a_kind_of(Chef::Search)
+ end
+
+ it "should build a Ferret search backend" do
+ Ferret::Index::Index.should_receive(:new).and_return(@mf)
+ Chef::Search.new
+ end
+end
+
+describe Chef::Search, "search method" do
+ before(:each) do
+ @mf = mock("Ferret::Index::Index", :null_object => true)
+ end
+
+ def do_search(type, query, &block)
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ cs = Chef::Search.new
+ if Kernel.block_given?
+ cs.search(type, query, &block)
+ else
+ cs.search(type, query)
+ end
+ end
+
+ it "should build the search query from the type and query provided" do
+ Ferret::Index::Index.stub!(:new).and_return(@mf)
+ cs = Chef::Search.new
+ cs.should_receive(:build_search_query).with(:node, "tag:monkey")
+ cs.search(:node, "tag:monkey")
+ end
+
+ it "should call search_each with the custom block if a block is given" do
+ cp = lambda { |n,u| "noting to do here" }
+ @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", { :limit => :all }, &cp)
+ do_search(:node, "tag:monkey", &cp)
+ end
+
+ it "should call search_each if a block is not given" do
+ @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", {:limit => :all})
+ do_search(:node, "tag:monkey")
+ end
+
+ it "should return the search results" do
+ @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", :limit => :all).and_return(true)
+ do_search(:node, "tag:monkey").should eql([])
+ end
+end \ No newline at end of file
diff --git a/chef/stories/chef-client b/chef/stories/chef-client
new file mode 100644
index 0000000000..93b8656268
--- /dev/null
+++ b/chef/stories/chef-client
@@ -0,0 +1,26 @@
+Story: Applying a meal with chef-client
+
+ As a systems administrator
+ I want to configure a system
+ So that I can drink more beer
+
+ Scenario: The client should be rejected if the node is not verified
+ Given the node 'latte'
+ When it runs the chef-client
+ Then it should register with the Chef Server
+
+ Scenario: The node registers for an OpenID
+
+ Given the node 'latte'
+ When it runs the chef-client
+ Then it should register with the Chef Server
+ And CouchDB should have a 'openid_registration_latte' document
+
+ Scenario: An administrator validates the OpenID Registration
+
+ Given the user 'openid.hjksolutions.com/adam'
+ And the openid registration for 'latte'
+ When the user validates the registration
+ Then the registration validation should be true
+
+
diff --git a/chef/stories/chef-client.rb b/chef/stories/chef-client.rb
new file mode 100644
index 0000000000..3e07b8a847
--- /dev/null
+++ b/chef/stories/chef-client.rb
@@ -0,0 +1,46 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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 File.expand_path(File.join(File.dirname(__FILE__), "story_helper"))
+
+steps_for(:chef_client) do
+ # Given the node 'latte'
+ Given("the node '$node'") do |node|
+ @client = Chef::Client.new
+ @client.build_node(node)
+ end
+
+ # Given it has not registered before
+ Given("it has not registered before") do
+ Chef::FileStore.load("registration", @client.safe_name)
+ end
+
+ # When it runs the chef-client
+
+ # Then it should register with the Chef Server
+
+ # Then CouchDB should have a 'openid_registration_latte' document
+
+ # Then the registration validation should be 'false'
+
+end
+
+with_steps_for(:chef_client) do
+ create_couchdb_database
+ run File.join(File.dirname(__FILE__), "chef-client")
+end \ No newline at end of file
diff --git a/chef/stories/story_helper.rb b/chef/stories/story_helper.rb
new file mode 100644
index 0000000000..cb0d1eb375
--- /dev/null
+++ b/chef/stories/story_helper.rb
@@ -0,0 +1,32 @@
+#
+# Author:: Adam Jacob (<adam@hjksolutions.com>)
+# Copyright:: Copyright (c) 2008 HJK Solutions, LLC
+# License:: Apache License, Version 2.0
+#
+# 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'
+require 'spec/story'
+
+require File.join(File.dirname(__FILE__), "..", "lib", "chef")
+Dir[File.join(File.dirname(__FILE__), 'lib', '**', '*.rb')].sort.each { |lib| require lib }
+
+Chef::Config.log_level(:fatal)
+Chef::Config[:couchdb_database] = "chef_test"
+
+def create_couchdb_database
+ c = Chef::CouchDB.new
+ c.create_db
+end
+
diff --git a/chef/tasks/rspec.rb b/chef/tasks/rspec.rb
new file mode 100644
index 0000000000..80b0a79557
--- /dev/null
+++ b/chef/tasks/rspec.rb
@@ -0,0 +1,54 @@
+require 'rubygems'
+require 'rake'
+require 'spec/rake/spectask'
+
+#desc "Run all examples"
+#Spec::Rake::SpecTask.new('spec') do |t|
+# t.spec_files = FileList[File.join(File.dirname(__FILE__), "..", "spec", "**", "*.rb")]
+#end
+
+require 'spec/rake/spectask'
+require 'spec/translator'
+
+CHEF_ROOT = File.join(File.dirname(__FILE__), "..")
+
+task :default => :spec
+
+desc "Run all specs in spec directory"
+Spec::Rake::SpecTask.new(:spec) do |t|
+ t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+end
+
+namespace :spec do
+ desc "Run all specs in spec directory with RCov"
+ Spec::Rake::SpecTask.new(:rcov) do |t|
+ t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ t.rcov = true
+ t.rcov_opts = lambda do
+ IO.readlines("#{CHEF_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
+ end
+ end
+
+ desc "Print Specdoc for all specs"
+ Spec::Rake::SpecTask.new(:doc) do |t|
+ t.spec_opts = ["--format", "specdoc", "--dry-run"]
+ t.spec_files = FileList['spec/**/*_spec.rb']
+ end
+
+ [:unit].each do |sub|
+ desc "Run the specs under spec/#{sub}"
+ Spec::Rake::SpecTask.new(sub) do |t|
+ t.spec_opts = ['--options', "\"#{CHEF_ROOT}/spec/spec.opts\""]
+ t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
+ end
+ end
+
+ desc "Translate/upgrade specs using the built-in translator"
+ task :translate do
+ translator = ::Spec::Translator.new
+ dir = CHEF_ROOT + '/spec'
+ translator.translate(dir, dir)
+ end
+end \ No newline at end of file