summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml30
-rw-r--r--CHANGELOG.md614
-rw-r--r--Gemfile15
-rw-r--r--Rakefile47
-rwxr-xr-xbin/chef-zero14
-rw-r--r--chef-zero.gemspec19
-rw-r--r--gemfiles/berkshelf.gemfile15
-rw-r--r--gemfiles/latest-chef.gemfile5
-rw-r--r--gemfiles/no-pedant.gemfile4
-rw-r--r--gemfiles/oc-chef-pedant.gemfile6
-rw-r--r--lib/chef_zero.rb3
-rw-r--r--lib/chef_zero/chef_data/acl_path.rb3
-rw-r--r--lib/chef_zero/chef_data/cookbook_data.rb5
-rw-r--r--lib/chef_zero/chef_data/data_normalizer.rb50
-rw-r--r--lib/chef_zero/chef_data/default_creator.rb16
-rw-r--r--lib/chef_zero/data_store/data_error.rb7
-rw-r--r--lib/chef_zero/data_store/default_facade.rb2
-rw-r--r--lib/chef_zero/endpoints/actor_default_key_endpoint.rb77
-rw-r--r--lib/chef_zero/endpoints/actor_endpoint.rb128
-rw-r--r--lib/chef_zero/endpoints/actor_key_endpoint.rb62
-rw-r--r--lib/chef_zero/endpoints/actor_keys_endpoint.rb129
-rw-r--r--lib/chef_zero/endpoints/actors_endpoint.rb76
-rw-r--r--lib/chef_zero/endpoints/containers_endpoint.rb12
-rw-r--r--lib/chef_zero/endpoints/controls_endpoint.rb15
-rw-r--r--lib/chef_zero/endpoints/cookbook_artifact_endpoint.rb24
-rw-r--r--lib/chef_zero/endpoints/cookbook_artifact_identifier_endpoint.rb68
-rw-r--r--lib/chef_zero/endpoints/cookbook_artifacts_endpoint.rb34
-rw-r--r--lib/chef_zero/endpoints/cookbook_version_endpoint.rb21
-rw-r--r--lib/chef_zero/endpoints/dummy_endpoint.rb31
-rw-r--r--lib/chef_zero/endpoints/node_endpoint.rb14
-rw-r--r--lib/chef_zero/endpoints/nodes_endpoint.rb35
-rw-r--r--lib/chef_zero/endpoints/organization_association_requests_endpoint.rb12
-rw-r--r--lib/chef_zero/endpoints/organization_user_base.rb14
-rw-r--r--lib/chef_zero/endpoints/organization_user_default_key_endpoint.rb16
-rw-r--r--lib/chef_zero/endpoints/organization_user_key_endpoint.rb17
-rw-r--r--lib/chef_zero/endpoints/organization_user_keys_endpoint.rb17
-rw-r--r--lib/chef_zero/endpoints/organization_users_endpoint.rb27
-rw-r--r--lib/chef_zero/endpoints/policies_endpoint.rb156
-rw-r--r--lib/chef_zero/endpoints/policy_endpoint.rb24
-rw-r--r--lib/chef_zero/endpoints/policy_group_endpoint.rb46
-rw-r--r--lib/chef_zero/endpoints/policy_group_policy_endpoint.rb84
-rw-r--r--lib/chef_zero/endpoints/policy_groups_endpoint.rb38
-rw-r--r--lib/chef_zero/endpoints/policy_revision_endpoint.rb23
-rw-r--r--lib/chef_zero/endpoints/policy_revisions_endpoint.rb15
-rw-r--r--lib/chef_zero/endpoints/principal_endpoint.rb17
-rw-r--r--lib/chef_zero/endpoints/rest_object_endpoint.rb24
-rw-r--r--lib/chef_zero/endpoints/server_api_version_endpoint.rb14
-rw-r--r--lib/chef_zero/rest_base.rb142
-rw-r--r--lib/chef_zero/rest_error_response.rb6
-rw-r--r--lib/chef_zero/rest_request.rb23
-rw-r--r--lib/chef_zero/rest_router.rb61
-rw-r--r--lib/chef_zero/rspec.rb46
-rw-r--r--lib/chef_zero/server.rb168
-rw-r--r--lib/chef_zero/version.rb2
-rw-r--r--spec/run_oc_pedant.rb197
-rw-r--r--spec/server_spec.rb5
-rw-r--r--spec/support/oc_pedant.rb46
58 files changed, 2252 insertions, 570 deletions
diff --git a/.gitignore b/.gitignore
index c13d06c..e29766c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,6 +8,7 @@ InstalledFiles
_yardoc
coverage
doc/
+binstubs/
lib/bundler/man
pkg
rdoc
diff --git a/.travis.yml b/.travis.yml
index c0b640e..4994db3 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,18 +1,34 @@
-rvm: 2.0
+rvm: 2.1
gemfile: Gemfile
+# This prevents testing branches that are created just for PRs
+branches:
+ only:
+ - master
+
+# Early warning system to catch if Rubygems breaks something
+before_install:
+ - gem update --system
+ - gem install bundler
+
sudo: false
-script: bundle exec rake pedant
+script:
+ - bundle update
+ - bundle exec rake pedant
matrix:
include:
- - rvm: 2.0
- rvm: 2.1
+ env: PEDANT_KNIFE_TESTS=true PEDANT_ALLOW_RVM=1
- rvm: 2.1
env: SINGLE_ORG=true
- rvm: 2.1
env: CHEF_FS=true
- rvm: 2.1
+ env:
+ - CHEF_FS=true
+ - "GEMFILE_MOD=\"gem 'chef', github: 'chef/chef'\""
+ - rvm: 2.1
env: FILE_STORE=true
- rvm: 2.1
script: bundle exec rake chef_spec
@@ -21,11 +37,3 @@ matrix:
script: bundle exec rake spec
env: TEST=rake_spec
- allow_failures:
- - rvm: 2.1
- gemfile: gemfiles/latest-chef.gemfile
- script: bundle exec rake chef_spec
- enc: TEST=chef_spec_latest
-# - rvm: 2.1.1
-# gemfile: gemfiles/berkshelf.gemfile
-# script: bundle exec rake berkshelf_spec
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 743232b..8627141 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,262 +1,572 @@
-Chef Zero CHANGELOG
-===================
+# Change Log
-$ 5.0 (pending)
+## [4.7.1](https://github.com/chef/chef-zero/tree/4.7.1) (2016-07-07)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.7.0...4.7.1)
-* updates to be compatible with current Chef Server 12 behaviors
-* update oc-chef-pedant to 2.0
-* remove chef-pedant support
+**Implemented enhancements:**
-# 4.2.3
+- Downgrade info log message to debug [\#221](https://github.com/chef/chef-zero/pull/221) ([stanhu](https://github.com/stanhu))
-* [PR#143](https://github.com/chef/chef-zero/pull/143): Fix server_scope: :context
+## [v4.7.0](https://github.com/chef/chef-zero/tree/v4.7.0) (2016-06-30)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.6.2...v4.7.0)
-# 4.2.2
+**Implemented enhancements:**
-* [PR#133](https://github.com/chef/chef-zero/pull/133): Fix RSpec
- helpers to use `chef_zero_opts` from let binding.
-* [PR#131](https://github.com/chef/chef-zero/pull/131): Adding new
- `server_on_port` method to the socketless server map.
+- Add external\_authentication\_uid to actors endpoint for querying [\#217](https://github.com/chef/chef-zero/pull/217) ([kmacgugan](https://github.com/kmacgugan))
-# 4.2.1
+**Merged pull requests:**
-* [PR#125](https://github.com/chef/chef-zero/pull/125): Don't polute
- global chef_server configs when running RSpec
+- Depend on rack \< 2 to restore Ruby 2.1 compat [\#219](https://github.com/chef/chef-zero/pull/219) ([tas50](https://github.com/tas50))
-# 4.2.0
+## [v4.6.2](https://github.com/chef/chef-zero/tree/v4.6.2) (2016-04-28)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.6.1...v4.6.2)
-* [PR#124](https://github.com/chef/chef-zero/pull/124): Bump ffi-yajl
- dependency
-* [PR#119](https://github.com/chef/chef-zero/pull/119): Add
- :organization and :data_scope options to RSpec support method
- `with_chef_server`
+**Fixed bugs:**
-# 4.1.0
+- Log responses only at debug log level [\#216](https://github.com/chef/chef-zero/pull/216) ([stevendanna](https://github.com/stevendanna))
-* [PR#121](https://github.com/chef/chef-zero/pull/121): Add Socketless
- mode.
-* [**Phil Dibowitz**](https://github.com/jaymzh):
- Added support for /version
+## [v4.6.1](https://github.com/chef/chef-zero/tree/v4.6.1) (2016-04-14)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.6.0...v4.6.1)
-# 4.0 (2/11/2014)
+**Fixed bugs:**
-- Add policyfile endpoints
-- Remove Ruby 1.8 and 1.9 support
+- Actually merge key data in user PUT response [\#214](https://github.com/chef/chef-zero/pull/214) ([jkeiser](https://github.com/jkeiser))
+- Fix users endpoint in OSC compat mode to use a data store URL [\#213](https://github.com/chef/chef-zero/pull/213) ([jkeiser](https://github.com/jkeiser))
-# 3.2 (9/26/2014)
+## [v4.6.0](https://github.com/chef/chef-zero/tree/v4.6.0) (2016-04-14)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.5.0...v4.6.0)
-* removed 'json' gem dependency, replaced it with 'ffi-yajl'
+**Implemented enhancements:**
-# 3.1.3 (9/3/2014)
+- Enable listening on more than one address [\#208](https://github.com/chef/chef-zero/pull/208) ([jaymzh](https://github.com/jaymzh))
+- Implemented GET /orgs/ORG/users/USER/keys\(/key\) endpoint recently added to server. [\#205](https://github.com/chef/chef-zero/pull/205) ([tylercloke](https://github.com/tylercloke))
+- Implement APIv1 behaviors [\#201](https://github.com/chef/chef-zero/pull/201) ([danielsdeleo](https://github.com/danielsdeleo))
+- Make user and client keys endpoints pass Pedant specs [\#199](https://github.com/chef/chef-zero/pull/199) ([jrunning](https://github.com/jrunning))
+- fix necessary for metadata gem [\#197](https://github.com/chef/chef-zero/pull/197) ([lamont-granquist](https://github.com/lamont-granquist))
-* fixes for running Chef local mode in multi-org mode
+**Fixed bugs:**
-# 3.1.2 (8/29/2014)
+- Fix bugs related to Array vs Enumerator vs Port for options\[:port/host\]. [\#212](https://github.com/chef/chef-zero/pull/212) ([tylercloke](https://github.com/tylercloke))
-* add default to rspec for cookbooks
-* add /organizations/NAME/organization/_acl as an alias for /organizations/NAME/organizations/_acl
+## [v4.5.0](https://github.com/chef/chef-zero/tree/v4.5.0) (2016-01-29)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.4.2...v4.5.0)
-# 3.1.1 (8/28/2014)
+**Merged pull requests:**
-* fix minor bug with unknown container acls
+- Run chef-zero against master Chef in travis [\#195](https://github.com/chef/chef-zero/pull/195) ([jkeiser](https://github.com/jkeiser))
+- Make ACLs for policies/policy\_groups/cookbook\_artifacts work [\#194](https://github.com/chef/chef-zero/pull/194) ([jkeiser](https://github.com/jkeiser))
+- Return 410 on /controls so we stop skipping that pedant spec. [\#192](https://github.com/chef/chef-zero/pull/192) ([randomcamel](https://github.com/randomcamel))
+- Enable container specs. [\#191](https://github.com/chef/chef-zero/pull/191) ([randomcamel](https://github.com/randomcamel))
+- Enable headers pedant tests [\#190](https://github.com/chef/chef-zero/pull/190) ([danielsdeleo](https://github.com/danielsdeleo))
+- Enable knife pedant tests [\#189](https://github.com/chef/chef-zero/pull/189) ([danielsdeleo](https://github.com/danielsdeleo))
+- Start running policy and cookbook artifact tests [\#187](https://github.com/chef/chef-zero/pull/187) ([jkeiser](https://github.com/jkeiser))
-# 3.1 (8/28/2014)
+## [v4.4.2](https://github.com/chef/chef-zero/tree/v4.4.2) (2016-01-15)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.4.1...v4.4.2)
-* New rspec data directives: organization, acl, group, container
-* Fix organizations POST to honor full_name
-* Fixes for enterprise rspec data loading
-* Fix invites not removing the invite when user is forcibly added to an org
+**Merged pull requests:**
-# 3.0 (7/22/2014)
+- Make hoovering and deleting parent dir work everywhere for cookbook\_artifacts [\#186](https://github.com/chef/chef-zero/pull/186) ([jkeiser](https://github.com/jkeiser))
+- Explain why omnibus/authz/authN/validation checks are skipped [\#185](https://github.com/chef/chef-zero/pull/185) ([danielsdeleo](https://github.com/danielsdeleo))
-* Enterprise Chef support (organizations, ACLs, groups, much more)
-* SSL support (@sawanoboly)
+## [v4.4.1](https://github.com/chef/chef-zero/tree/v4.4.1) (2016-01-14)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.4.0...v4.4.1)
-# 2.2 (6/18/2014)
+**Merged pull requests:**
-* allow port ranges to be passed in as enumerables, which will be tried in sequence until one works: `ChefZero::Server.new(:port => 80.upto(100))`
+- Only test master branch and PRs [\#184](https://github.com/chef/chef-zero/pull/184) ([danielsdeleo](https://github.com/danielsdeleo))
+- Internal orgs appears to be unused in oc-chef-pedant [\#183](https://github.com/chef/chef-zero/pull/183) ([danielsdeleo](https://github.com/danielsdeleo))
+- Fix cookbook\_artifact rspec [\#182](https://github.com/chef/chef-zero/pull/182) ([jkeiser](https://github.com/jkeiser))
+- Point chef-server back to master [\#180](https://github.com/chef/chef-zero/pull/180) ([thommay](https://github.com/thommay))
+- Ignore the universe endpoint tests in pedant [\#176](https://github.com/chef/chef-zero/pull/176) ([thommay](https://github.com/thommay))
-# 2.1.5 (6/2/2014)
+## [v4.4.0](https://github.com/chef/chef-zero/tree/v4.4.0) (2015-12-11)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.3.2...v4.4.0)
-* fix issue with :single_org => <value> not being honored
+**Closed issues:**
-# 2.1.4 (5/27/2014)
+- Please bump hashie version if possible [\#97](https://github.com/chef/chef-zero/issues/97)
-* fix issue with global Thread.exit_on_exception being set
+**Merged pull requests:**
-# 2.1.3 (5/27/2014)
+- ChefZero::RSpec support for cookbook\_artifacts. [\#179](https://github.com/chef/chef-zero/pull/179) ([randomcamel](https://github.com/randomcamel))
+- /cookbook\_artifacts support for in-memory and FILE\_STORE backends \(not ChefFS\) [\#178](https://github.com/chef/chef-zero/pull/178) ([randomcamel](https://github.com/randomcamel))
+- Update and refactor policy and policy\_groups endpoints [\#177](https://github.com/chef/chef-zero/pull/177) ([jkeiser](https://github.com/jkeiser))
+- Point at master of oc-chef-pedant and chef [\#174](https://github.com/chef/chef-zero/pull/174) ([stevendanna](https://github.com/stevendanna))
+- Upgrade pedant, and enable running in ChefFS mode [\#173](https://github.com/chef/chef-zero/pull/173) ([randomcamel](https://github.com/randomcamel))
+- Implement the /policies and /policy\_groups API routes [\#172](https://github.com/chef/chef-zero/pull/172) ([randomcamel](https://github.com/randomcamel))
+- Add gemspec files to allow bundler to run from the gem [\#169](https://github.com/chef/chef-zero/pull/169) ([ksubrama](https://github.com/ksubrama))
-* rspec: default port to 8900 to not conflict with normal default port
-* rspec: when chef_zero_opts is set, check if current server has those options before continuing
+## [v4.3.2](https://github.com/chef/chef-zero/tree/v4.3.2) (2015-09-30)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.3.1...v4.3.2)
-# 2.1.2 (5/27/2014)
+## [v4.3.1](https://github.com/chef/chef-zero/tree/v4.3.1) (2015-09-30)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.3.0...v4.3.1)
-* fix build_uri (and thus cookbook downloads)
+**Fixed bugs:**
-# 2.1.1 (5/26/2014)
+- chefspec client creation test broken by \#117 [\#165](https://github.com/chef/chef-zero/issues/165)
+- Translate admin="true" to admin=true [\#166](https://github.com/chef/chef-zero/pull/166) ([jkeiser](https://github.com/jkeiser))
-* flip defaults off in V1ToV2Adapater, allowing most chef tests to pass against 2.1.1
+## [v4.3.0](https://github.com/chef/chef-zero/tree/v4.3.0) (2015-09-02)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.2.3...v4.3.0)
-# 2.1 (5/26/2014)
+**Implemented enhancements:**
-* **Multi-tenancy!** If you set :single_org => nil when starting the server, you will gain /organizations/* at the beginning of all URLs. Internally, all endpoints are rooted at /organizations/ORG anyway, there is just a translation that goes on to add /organizations/single_org to the URL when someone hits chef-zero.
-* Fixes to support chef-zero local mode passing pedant
+- Allow Hashie to float to 3.x \(no need to be so specific\) [\#164](https://github.com/chef/chef-zero/pull/164) ([jkeiser](https://github.com/jkeiser))
+- Server api version [\#155](https://github.com/chef/chef-zero/pull/155) ([andrewjamesbrown](https://github.com/andrewjamesbrown))
+- Add /organizations/NAME/nodes/NAME/\_identifiers endpoint [\#152](https://github.com/chef/chef-zero/pull/152) ([andrewjamesbrown](https://github.com/andrewjamesbrown))
+- CS12 Support [\#117](https://github.com/chef/chef-zero/pull/117) ([marcparadise](https://github.com/marcparadise))
-# 2.0.2 (1/20/2014)
+**Fixed bugs:**
-* Fix a series of typos in the README
-* Read JSON, not a file path in `from_json`
-* Fix IPV6 support
-* Remove moneta as a dependency
+- Adding back logic to delete the association request when adding a user to an org \(as well as adding the user to the groups\) [\#160](https://github.com/chef/chef-zero/pull/160) ([tyler-ball](https://github.com/tyler-ball))
+- Switch to pedant in chef-server repo [\#151](https://github.com/chef/chef-zero/pull/151) ([andrewjamesbrown](https://github.com/andrewjamesbrown))
+- Update gem dependencies [\#146](https://github.com/chef/chef-zero/pull/146) ([andrewjamesbrown](https://github.com/andrewjamesbrown))
+- Remove dependency on chef [\#140](https://github.com/chef/chef-zero/pull/140) ([terceiro](https://github.com/terceiro))
-# 2.0.1 (1/3/2014)
+**Merged pull requests:**
-* Make playground items more semantic
-* Fix an issue where an incorrect number of parameters was passed in `environments/NAME/nodes` endpoint
-* Fix an issue where the `data_store` was not yet initialized in the server
+- Autogenerated changelog [\#163](https://github.com/chef/chef-zero/pull/163) ([jkeiser](https://github.com/jkeiser))
-# 2.0.0 (12/17/2013)
+## [v4.2.3](https://github.com/chef/chef-zero/tree/v4.2.3) (2015-06-19)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.2.2...v4.2.3)
-* Remove Puma (and `--socket` option)
-* Use a cleaner threading approach
-* Implement a better `running?` check
+**Merged pull requests:**
-# 1.7.3
+- Make server\_scope: :context work again [\#143](https://github.com/chef/chef-zero/pull/143) ([jkeiser](https://github.com/jkeiser))
-* (Backport) Read JSON, not a file path in `from_json`
+## [v4.2.2](https://github.com/chef/chef-zero/tree/v4.2.2) (2015-05-18)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.2.1...v4.2.2)
-# 1.6.3
+**Merged pull requests:**
-* (Backport) Read JSON, not a file path in `from_json`
+- Update version and changelog for 4.2.2 [\#134](https://github.com/chef/chef-zero/pull/134) ([danielsdeleo](https://github.com/danielsdeleo))
+- Access server opts in example context not describe context [\#133](https://github.com/chef/chef-zero/pull/133) ([danielsdeleo](https://github.com/danielsdeleo))
+- Adding `server\_on\_port` method to socketless server map [\#131](https://github.com/chef/chef-zero/pull/131) ([tyler-ball](https://github.com/tyler-ball))
+- Ignore .ruby-version [\#98](https://github.com/chef/chef-zero/pull/98) ([raskchanky](https://github.com/raskchanky))
-# 1.5.5
+## [v4.2.1](https://github.com/chef/chef-zero/tree/v4.2.1) (2015-04-07)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.2.0...v4.2.1)
-* Fix issue with - in term (name:a-b)
+**Merged pull requests:**
-# 1.5.4
+- Don't pollute global Chef server options [\#125](https://github.com/chef/chef-zero/pull/125) ([jkeiser](https://github.com/jkeiser))
-* Fix issue where run_lists in format cookbook::recipe@version do not depsolve
+## [v4.2.0](https://github.com/chef/chef-zero/tree/v4.2.0) (2015-04-06)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.1.0...v4.2.0)
-# 1.5.3
+**Merged pull requests:**
-* Add Server: chef-zero header to response
+- bump ffi-yajl dep [\#124](https://github.com/chef/chef-zero/pull/124) ([lamont-granquist](https://github.com/lamont-granquist))
+- Add :organization and :data\_scope options to with\_chef\_server [\#119](https://github.com/chef/chef-zero/pull/119) ([jkeiser](https://github.com/jkeiser))
-# 1.5.2
+## [v4.1.0](https://github.com/chef/chef-zero/tree/v4.1.0) (2015-04-01)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v4.0...v4.1.0)
-* Fix a couple of search query issues (make parentheses and NOT term:value work)
+**Merged pull requests:**
-# 1.5.1
+- Socketless Requests [\#121](https://github.com/chef/chef-zero/pull/121) ([danielsdeleo](https://github.com/danielsdeleo))
+- Partially Revert 1b2a6e5f107254cce8200a4750035b30265ae0c8 [\#120](https://github.com/chef/chef-zero/pull/120) ([danielsdeleo](https://github.com/danielsdeleo))
+- Policyfile Revision ID Validation. [\#116](https://github.com/chef/chef-zero/pull/116) ([danielsdeleo](https://github.com/danielsdeleo))
+- Use a Chef version compatible with chef-zero 4.x [\#115](https://github.com/chef/chef-zero/pull/115) ([danielsdeleo](https://github.com/danielsdeleo))
+- Support /version; fix some global URIs [\#113](https://github.com/chef/chef-zero/pull/113) ([jaymzh](https://github.com/jaymzh))
-* Add Unix domain socket support (e.g. chef-zero --socket /tmp/chef-zero.sock) (stevendanna)
+## [v4.0](https://github.com/chef/chef-zero/tree/v4.0) (2015-02-11)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.2.1...v4.0)
-# 1.5
+**Closed issues:**
-* Add -d option for daemon mode (sethvargo)
-* Fix bug with cookbook metadata.rb files that rely on __FILE__
+- Upgrading to chef-client 12 packages appear to break on TK's chef-zero provisioner [\#108](https://github.com/chef/chef-zero/issues/108)
+- The local\_mode on chef 12.0.0.alpha.2 can't create object to local filesystem. [\#99](https://github.com/chef/chef-zero/issues/99)
-# 1.4
+**Merged pull requests:**
-* Run with downgraded Puma 1.6 in order to work on Windows (2.x doesn't yet)
+- Policyfile get/set API [\#111](https://github.com/chef/chef-zero/pull/111) ([danielsdeleo](https://github.com/danielsdeleo))
-# 1.3
+## [v3.2.1](https://github.com/chef/chef-zero/tree/v3.2.1) (2014-11-26)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.2.1...v3.2.1)
-* Fix bug with search when JSON contains the same key in different places
+**Closed issues:**
-# 1.2.1
+- missing `else`? [\#102](https://github.com/chef/chef-zero/issues/102)
-* Fix search when JSON contains integers
+**Merged pull requests:**
-# 1.2
+- Version bump for 3.2.1. [\#105](https://github.com/chef/chef-zero/pull/105) ([sersut](https://github.com/sersut))
+- fix: should set https to rack.url\_scheme \#87 [\#104](https://github.com/chef/chef-zero/pull/104) ([sawanoboly](https://github.com/sawanoboly))
+- Add option for logging to a file. [\#103](https://github.com/chef/chef-zero/pull/103) ([jaymzh](https://github.com/jaymzh))
+- add CORS header [\#96](https://github.com/chef/chef-zero/pull/96) ([smith](https://github.com/smith))
-* Allow rspec users to specify cookbook NAME, VERSION, { :frozen => true }
-* Documentation fix
+## [v2.2.1](https://github.com/chef/chef-zero/tree/v2.2.1) (2014-10-08)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.2...v2.2.1)
-# 1.1.3
+## [v3.2](https://github.com/chef/chef-zero/tree/v3.2) (2014-09-27)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.1.3...v3.2)
-* Return better defaults for cookbooks
-* Support /cookbook_versions?cookbook_versions=... query parameter
-* Fix server crash when cookbook has multiple identical checksums
+**Closed issues:**
-# 1.1.2
+- chef-zero not copying data\_bags into cache [\#83](https://github.com/chef/chef-zero/issues/83)
+- Wrong generated client keys with Ruby 1.9.2 [\#65](https://github.com/chef/chef-zero/issues/65)
-* Allow rspec users to specify the same data twice (overwrites)
+**Merged pull requests:**
-# 1.1.1
+- Removing 'json' gem dependency, replacing with 'ffi-yajl' [\#93](https://github.com/chef/chef-zero/pull/93) ([tyler-ball](https://github.com/tyler-ball))
-* Fix broken rspec functionality (jkeiser, reset)
+## [v3.1.3](https://github.com/chef/chef-zero/tree/v3.1.3) (2014-09-04)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.1.2...v3.1.3)
-# 1.1
+**Merged pull requests:**
-* Create plugin system to allow other storage besides memory
+- Pass base URI to V1 data store only in true single org mode [\#89](https://github.com/chef/chef-zero/pull/89) ([jkeiser](https://github.com/jkeiser))
-# 1.0.1
+## [v3.1.2](https://github.com/chef/chef-zero/tree/v3.1.2) (2014-08-29)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.1.1...v3.1.2)
-* Fix depsolver crash with frozen version strings (sethvargo)
+## [v3.1.1](https://github.com/chef/chef-zero/tree/v3.1.1) (2014-08-29)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.1...v3.1.1)
-# 1.0
+## [v3.1](https://github.com/chef/chef-zero/tree/v3.1) (2014-08-29)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.0...v3.1)
-* Increased testing of server
+## [v3.0](https://github.com/chef/chef-zero/tree/v3.0) (2014-08-22)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v3.0.0.rc.1...v3.0)
-# 0.9.13
+**Implemented enhancements:**
-* Remove extra require of 'thin' so rspec users don't get broke
+- Make Enterprise Chef pass oc-chef-pedant [\#73](https://github.com/chef/chef-zero/issues/73)
+- Add Enterprise Chef users endpoints [\#72](https://github.com/chef/chef-zero/issues/72)
+- Add Enterprise Chef containers endpoints [\#71](https://github.com/chef/chef-zero/issues/71)
+- Add Enterprise Chef groups endpoints [\#70](https://github.com/chef/chef-zero/issues/70)
+- Add Enterprise Chef acls endpoints [\#69](https://github.com/chef/chef-zero/issues/69)
+- Add Enterprise Chef organizations endpoints [\#68](https://github.com/chef/chef-zero/issues/68)
-# 0.9.12
+**Closed issues:**
-* Switch from thin to puma (sethvargo)
+- SSL support [\#86](https://github.com/chef/chef-zero/issues/86)
-# 0.9.11
+## [v3.0.0.rc.1](https://github.com/chef/chef-zero/tree/v3.0.0.rc.1) (2014-08-22)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.2...v3.0.0.rc.1)
-* Support full cookbook metadata.rb syntax, including "depends"
+**Closed issues:**
-# 0.9.10
+- Vagrant on windows fails to load Chef-Zero due to Chef dependencies [\#55](https://github.com/chef/chef-zero/issues/55)
-* Add -d flag to print debug output (sethvargo)
+**Merged pull requests:**
-# 0.9.9
+- Support ssl \(--\[no-\]ssl option\) [\#87](https://github.com/chef/chef-zero/pull/87) ([sawanoboly](https://github.com/sawanoboly))
+- Get enterprise chef-zero passing oc-chef-pedant [\#84](https://github.com/chef/chef-zero/pull/84) ([jkeiser](https://github.com/jkeiser))
+- waffle.io Badge [\#74](https://github.com/chef/chef-zero/pull/74) ([waffle-iron](https://github.com/waffle-iron))
-* Remove chef as a dependency so we can run on jruby (reset)
-* Server assumes json is acceptable if Accept header is not sent (stevendanna)
+## [v2.2](https://github.com/chef/chef-zero/tree/v2.2) (2014-06-18)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.1.5...v2.2)
-# 0.9.8
+**Merged pull requests:**
-* Support runlists with a::b in them in depsolver
+- Allow server to try multiple ports [\#67](https://github.com/chef/chef-zero/pull/67) ([jkeiser](https://github.com/jkeiser))
-# 0.9.7
+## [v2.1.5](https://github.com/chef/chef-zero/tree/v2.1.5) (2014-06-03)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.1.4...v2.1.5)
-* Return file URLs and other important things in depsolver response
+**Merged pull requests:**
-# 0.9.6
+- Honor :single\_org =\> 'orgname' parameter everywhere [\#66](https://github.com/chef/chef-zero/pull/66) ([jkeiser](https://github.com/jkeiser))
-* Make 404 a JSON response
+## [v2.1.4](https://github.com/chef/chef-zero/tree/v2.1.4) (2014-05-28)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.1.3...v2.1.4)
-# 0.9.5
+## [v2.1.3](https://github.com/chef/chef-zero/tree/v2.1.3) (2014-05-27)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.1.2...v2.1.3)
-* Fix crash in 405 error response generator
-* Add ability to verify request/response pairs from rspec api
+## [v2.1.2](https://github.com/chef/chef-zero/tree/v2.1.2) (2014-05-27)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.1.1...v2.1.2)
-# 0.9.4
+## [v2.1.1](https://github.com/chef/chef-zero/tree/v2.1.1) (2014-05-27)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.1...v2.1.1)
-* Ruby 1.8.7 support
+## [v2.1](https://github.com/chef/chef-zero/tree/v2.1) (2014-05-26)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.6.3...v2.1)
-# 0.9.3
+**Closed issues:**
-* rspec fixes:
- - Faster (0 retries)
- - Work with more than one test
- - Allow tags on when_the_chef_server
-* make 500 response return actual exception info
+- Is there a good way to detect if cookbook is being run using chef-zero? [\#63](https://github.com/chef/chef-zero/issues/63)
+- Not loading roles [\#61](https://github.com/chef/chef-zero/issues/61)
+- How to handle changing url of ChefZero during chef-client run [\#59](https://github.com/chef/chef-zero/issues/59)
+- data\_bag\_item fails in definition block executions [\#58](https://github.com/chef/chef-zero/issues/58)
-# 0.9.2
+**Merged pull requests:**
-* Speed increase for rspec (only start server once)
-* Support CTRL+C when running rspec chef-zero tests
+- Add multi-tenancy support and chef local mode tests [\#64](https://github.com/chef/chef-zero/pull/64) ([jkeiser](https://github.com/jkeiser))
-# 0.9.1
+## [v1.6.3](https://github.com/chef/chef-zero/tree/v1.6.3) (2014-02-10)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.7.3...v1.6.3)
-* Switch from webrick -> thin
-* Bugfixes
+**Closed issues:**
-# 0.9
+- chef-zero 404'ing on data bag items [\#56](https://github.com/chef/chef-zero/issues/56)
+- Errno::ENAMETOOLONG [\#53](https://github.com/chef/chef-zero/issues/53)
+- Backport fix for Isssue \#48 to the 1.x line [\#51](https://github.com/chef/chef-zero/issues/51)
+- Search strange behavior [\#42](https://github.com/chef/chef-zero/issues/42)
-* Initial code-complete release with working server
+## [v1.7.3](https://github.com/chef/chef-zero/tree/v1.7.3) (2014-01-22)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.0.2...v1.7.3)
+
+**Closed issues:**
+
+- search query fails when using parenthesis [\#38](https://github.com/chef/chef-zero/issues/38)
+
+## [v2.0.2](https://github.com/chef/chef-zero/tree/v2.0.2) (2014-01-21)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.0.1...v2.0.2)
+
+**Closed issues:**
+
+- Parsing metadata blows up on evaluating metadata.json. [\#48](https://github.com/chef/chef-zero/issues/48)
+- depsolve endpoint needs informative error messages [\#32](https://github.com/chef/chef-zero/issues/32)
+
+**Merged pull requests:**
+
+- Read JSON, not a file path [\#52](https://github.com/chef/chef-zero/pull/52) ([sethvargo](https://github.com/sethvargo))
+- Fix IPV6 support [\#50](https://github.com/chef/chef-zero/pull/50) ([sethvargo](https://github.com/sethvargo))
+- Fix typo [\#47](https://github.com/chef/chef-zero/pull/47) ([gregkare](https://github.com/gregkare))
+
+## [v2.0.1](https://github.com/chef/chef-zero/tree/v2.0.1) (2014-01-03)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v2.0.0...v2.0.1)
+
+**Merged pull requests:**
+
+- Fix clear data when no data was added to chef zero [\#46](https://github.com/chef/chef-zero/pull/46) ([alex-slynko](https://github.com/alex-slynko))
+- Fix an issue with an incorrect number of parameters passed to build\_uri [\#45](https://github.com/chef/chef-zero/pull/45) ([sethvargo](https://github.com/sethvargo))
+- Make playground items more semantic [\#44](https://github.com/chef/chef-zero/pull/44) ([sethvargo](https://github.com/sethvargo))
+
+## [v2.0.0](https://github.com/chef/chef-zero/tree/v2.0.0) (2013-12-17)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.7.2...v2.0.0)
+
+**Closed issues:**
+
+- Not found returned for all roles [\#34](https://github.com/chef/chef-zero/issues/34)
+
+**Merged pull requests:**
+
+- Remove puma and fork a subprocess [\#43](https://github.com/chef/chef-zero/pull/43) ([sethvargo](https://github.com/sethvargo))
+
+## [v1.7.2](https://github.com/chef/chef-zero/tree/v1.7.2) (2013-11-19)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.7.1...v1.7.2)
+
+**Merged pull requests:**
+
+- Make Server Options Configurable in RSpec Helper [\#41](https://github.com/chef/chef-zero/pull/41) ([danielsdeleo](https://github.com/danielsdeleo))
+
+## [v1.7.1](https://github.com/chef/chef-zero/tree/v1.7.1) (2013-11-03)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.7...v1.7.1)
+
+**Implemented enhancements:**
+
+- Find a pure-Ruby server solution [\#36](https://github.com/chef/chef-zero/issues/36)
+
+## [v1.7](https://github.com/chef/chef-zero/tree/v1.7) (2013-11-02)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.7.beta.1...v1.7)
+
+## [v1.7.beta.1](https://github.com/chef/chef-zero/tree/v1.7.beta.1) (2013-11-02)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.6.2...v1.7.beta.1)
+
+**Merged pull requests:**
+
+- improve depsolver error message [\#40](https://github.com/chef/chef-zero/pull/40) ([lamont-granquist](https://github.com/lamont-granquist))
+
+## [v1.6.2](https://github.com/chef/chef-zero/tree/v1.6.2) (2013-10-11)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.6.1...v1.6.2)
+
+## [v1.6.1](https://github.com/chef/chef-zero/tree/v1.6.1) (2013-10-11)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.6...v1.6.1)
+
+**Closed issues:**
+
+- \[\* TO \*\] search syntax doesn't work [\#37](https://github.com/chef/chef-zero/issues/37)
+
+**Merged pull requests:**
+
+- Range queries with smoke test [\#35](https://github.com/chef/chef-zero/pull/35) ([mattgleeson](https://github.com/mattgleeson))
+
+## [v1.6](https://github.com/chef/chef-zero/tree/v1.6) (2013-09-13)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5.6...v1.6)
+
+## [v1.5.6](https://github.com/chef/chef-zero/tree/v1.5.6) (2013-09-13)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5.5...v1.5.6)
+
+## [v1.5.5](https://github.com/chef/chef-zero/tree/v1.5.5) (2013-08-08)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5.4...v1.5.5)
+
+**Closed issues:**
+
+- Incorrect search behavior when a query parameter contains a '-' character [\#31](https://github.com/chef/chef-zero/issues/31)
+- databag.save throws 405 "Method not allowed" [\#29](https://github.com/chef/chef-zero/issues/29)
+- How can I validate if I am running in Chef-Zero mode? [\#28](https://github.com/chef/chef-zero/issues/28)
+- License missing from gemspec [\#26](https://github.com/chef/chef-zero/issues/26)
+
+**Merged pull requests:**
+
+- fixup end\_range typos [\#30](https://github.com/chef/chef-zero/pull/30) ([mattgleeson](https://github.com/mattgleeson))
+- Add license to gemspec \(fixes \#26\) [\#27](https://github.com/chef/chef-zero/pull/27) ([sethvargo](https://github.com/sethvargo))
+
+## [v1.5.4](https://github.com/chef/chef-zero/tree/v1.5.4) (2013-07-12)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5.3...v1.5.4)
+
+**Closed issues:**
+
+- Issue with [\#25](https://github.com/chef/chef-zero/issues/25)
+- Typo error in README [\#24](https://github.com/chef/chef-zero/issues/24)
+- Add a CHANGELOG [\#11](https://github.com/chef/chef-zero/issues/11)
+
+## [v1.5.3](https://github.com/chef/chef-zero/tree/v1.5.3) (2013-06-28)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5.2...v1.5.3)
+
+**Closed issues:**
+
+- Chef Zero should set a server header for identification [\#23](https://github.com/chef/chef-zero/issues/23)
+- Data bag search of the form "x:y AND NOT z:w" fails [\#22](https://github.com/chef/chef-zero/issues/22)
+
+## [v1.5.2](https://github.com/chef/chef-zero/tree/v1.5.2) (2013-06-27)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5.1...v1.5.2)
+
+## [v1.5.1](https://github.com/chef/chef-zero/tree/v1.5.1) (2013-06-19)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.5...v1.5.1)
+
+**Closed issues:**
+
+- Developing Vagrant-Chef-Zero [\#21](https://github.com/chef/chef-zero/issues/21)
+
+**Merged pull requests:**
+
+- Allow chef-zero to listen on Unix domain socket. [\#20](https://github.com/chef/chef-zero/pull/20) ([stevendanna](https://github.com/stevendanna))
+
+## [v1.5](https://github.com/chef/chef-zero/tree/v1.5) (2013-06-12)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.4...v1.5)
+
+**Merged pull requests:**
+
+- Support daemon mode [\#19](https://github.com/chef/chef-zero/pull/19) ([sethvargo](https://github.com/sethvargo))
+
+## [v1.4](https://github.com/chef/chef-zero/tree/v1.4) (2013-06-07)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.4.0.alpha...v1.4)
+
+**Merged pull requests:**
+
+- Downgrade Puma so Chef Zero runs on Windows [\#18](https://github.com/chef/chef-zero/pull/18) ([sethvargo](https://github.com/sethvargo))
+
+## [v1.4.0.alpha](https://github.com/chef/chef-zero/tree/v1.4.0.alpha) (2013-06-07)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.3...v1.4.0.alpha)
+
+**Closed issues:**
+
+- Searching for a node with an exact name fails [\#15](https://github.com/chef/chef-zero/issues/15)
+
+## [v1.3](https://github.com/chef/chef-zero/tree/v1.3) (2013-06-06)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.2.1...v1.3)
+
+**Closed issues:**
+
+- Searching is Broken [\#14](https://github.com/chef/chef-zero/issues/14)
+
+## [v1.2.1](https://github.com/chef/chef-zero/tree/v1.2.1) (2013-06-05)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.2...v1.2.1)
+
+**Closed issues:**
+
+- Allow seeding of Data [\#13](https://github.com/chef/chef-zero/issues/13)
+
+## [v1.2](https://github.com/chef/chef-zero/tree/v1.2) (2013-06-03)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.1.3...v1.2)
+
+**Merged pull requests:**
+
+- Fix usage info to be consistent with latest version [\#12](https://github.com/chef/chef-zero/pull/12) ([andrewgross](https://github.com/andrewgross))
+
+## [v1.1.3](https://github.com/chef/chef-zero/tree/v1.1.3) (2013-05-30)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.1.2...v1.1.3)
+
+## [v1.1.2](https://github.com/chef/chef-zero/tree/v1.1.2) (2013-05-29)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.1.1...v1.1.2)
+
+## [v1.1.1](https://github.com/chef/chef-zero/tree/v1.1.1) (2013-05-28)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.1...v1.1.1)
+
+**Merged pull requests:**
+
+- fix undefined method .list on CookbookData [\#10](https://github.com/chef/chef-zero/pull/10) ([reset](https://github.com/reset))
+
+## [v1.1](https://github.com/chef/chef-zero/tree/v1.1) (2013-05-28)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.0.1...v1.1)
+
+## [v1.0.1](https://github.com/chef/chef-zero/tree/v1.0.1) (2013-05-20)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v1.0...v1.0.1)
+
+**Merged pull requests:**
+
+- Fix frozen version [\#9](https://github.com/chef/chef-zero/pull/9) ([sethvargo](https://github.com/sethvargo))
+
+## [v1.0](https://github.com/chef/chef-zero/tree/v1.0) (2013-05-20)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.13...v1.0)
+
+## [v0.9.13](https://github.com/chef/chef-zero/tree/v0.9.13) (2013-05-20)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.12...v0.9.13)
+
+## [v0.9.12](https://github.com/chef/chef-zero/tree/v0.9.12) (2013-05-20)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.11...v0.9.12)
+
+**Merged pull requests:**
+
+- Move to Puma [\#8](https://github.com/chef/chef-zero/pull/8) ([sethvargo](https://github.com/sethvargo))
+
+## [v0.9.11](https://github.com/chef/chef-zero/tree/v0.9.11) (2013-05-19)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.9...v0.9.11)
+
+**Closed issues:**
+
+- no port found in cookbook url - can't download cookbooks unless listening on port 80 [\#5](https://github.com/chef/chef-zero/issues/5)
+
+**Merged pull requests:**
+
+- Add '-d' flag [\#7](https://github.com/chef/chef-zero/pull/7) ([sethvargo](https://github.com/sethvargo))
+
+## [v0.9.9](https://github.com/chef/chef-zero/tree/v0.9.9) (2013-05-14)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.8...v0.9.9)
+
+**Closed issues:**
+
+- HTTP 500 Returned when Accept Header isn't set. [\#3](https://github.com/chef/chef-zero/issues/3)
+- knife plugin? [\#2](https://github.com/chef/chef-zero/issues/2)
+- Cannot give a specific recipe from chef-client [\#1](https://github.com/chef/chef-zero/issues/1)
+
+**Merged pull requests:**
+
+- Remove dependency on Chef gem \(adds JRuby support\) [\#6](https://github.com/chef/chef-zero/pull/6) ([reset](https://github.com/reset))
+- Assume application/json is acceptable if no Accept header was sent. [\#4](https://github.com/chef/chef-zero/pull/4) ([stevendanna](https://github.com/stevendanna))
+
+## [v0.9.8](https://github.com/chef/chef-zero/tree/v0.9.8) (2013-04-29)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.7...v0.9.8)
+
+## [v0.9.7](https://github.com/chef/chef-zero/tree/v0.9.7) (2013-04-17)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.6...v0.9.7)
+
+## [v0.9.6](https://github.com/chef/chef-zero/tree/v0.9.6) (2013-04-17)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.5...v0.9.6)
+
+## [v0.9.5](https://github.com/chef/chef-zero/tree/v0.9.5) (2013-01-21)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.4...v0.9.5)
+
+## [v0.9.4](https://github.com/chef/chef-zero/tree/v0.9.4) (2013-01-18)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.3...v0.9.4)
+
+## [v0.9.3](https://github.com/chef/chef-zero/tree/v0.9.3) (2013-01-12)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.2...v0.9.3)
+
+## [v0.9.2](https://github.com/chef/chef-zero/tree/v0.9.2) (2012-12-31)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9.1...v0.9.2)
+
+## [v0.9.1](https://github.com/chef/chef-zero/tree/v0.9.1) (2012-12-24)
+[Full Changelog](https://github.com/chef/chef-zero/compare/v0.9...v0.9.1)
+
+## [v0.9](https://github.com/chef/chef-zero/tree/v0.9) (2012-12-24)
+
+
+\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)* \ No newline at end of file
diff --git a/Gemfile b/Gemfile
index 6d3fbdf..ca2d322 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,9 +1,20 @@
source 'https://rubygems.org'
gemspec
-gem 'rest-client', :github => 'chef/rest-client'
+# gem 'rest-client', :github => 'chef/rest-client'
gem 'oc-chef-pedant', :github => 'chef/chef-server'
-gem 'chef', :github => 'chef/chef', :tag => '12.4.1'
+group :changelog do
+ gem "github_changelog_generator"
+end
+# bundler resolve failure on "rspec_junit_formatter"
+# gem 'chef-pedant', :github => 'opscode/chef-pedant', :ref => "server-cli-option"
+
+# gem 'chef', :github => 'chef/chef', :branch => 'jk/policies-acls'
+
+if ENV['GEMFILE_MOD']
+ puts "GEMFILE_MOD: #{ENV['GEMFILE_MOD']}"
+ instance_eval(ENV['GEMFILE_MOD'])
+end
diff --git a/Rakefile b/Rakefile
index 1ce1b92..c43a6a3 100644
--- a/Rakefile
+++ b/Rakefile
@@ -3,21 +3,43 @@ require 'bundler/gem_tasks'
require 'chef_zero/version'
+def run_oc_pedant(env={})
+ ENV.update(env)
+ require File.expand_path('spec/run_oc_pedant')
+end
+
+ENV_DOCS = <<END
+Environment:
+ - RSPEC_OPTS Options to pass to RSpec
+ e.g. RSPEC_OPTS="--fail-fast --profile 5"
+ - PEDANT_OPTS Options to pass to oc-chef-pedant
+ e.g. PEDANT_OPTS="--focus-keys --skip-users"
+ - LOG_LEVEL Set the log level (default: warn)
+ e.g. LOG_LEVEL=debug
+END
+
task :default => :pedant
-desc "run specs"
+desc "Run specs"
task :spec do
system('rspec spec/*_spec.rb')
end
-desc "run oc pedant"
-task :pedant do
- require File.expand_path('spec/run_oc_pedant')
+desc "Run oc-chef-pedant\n\n#{ENV_DOCS}"
+task :pedant => :oc_pedant
+
+desc "Run oc-chef-pedant with CHEF_FS set\n\n#{ENV_DOCS}"
+task :cheffs do
+ run_oc_pedant('CHEF_FS' => 'yes')
+end
+
+desc "Run oc-chef-pedant with FILE_STORE set\n\n#{ENV_DOCS}"
+task :filestore do
+ run_oc_pedant('FILE_STORE' => 'yes')
end
-desc "run oc pedant"
task :oc_pedant do
- require File.expand_path('spec/run_oc_pedant')
+ run_oc_pedant
end
task :chef_spec do
@@ -29,3 +51,16 @@ task :berkshelf_spec do
gem_path = Bundler.environment.specs['berkshelf'].first.full_gem_path
system("cd #{gem_path} && thor spec:ci")
end
+
+begin
+ require "github_changelog_generator/task"
+
+ GitHubChangelogGenerator::RakeTask.new :changelog do |config|
+ config.future_release = ChefZero::VERSION
+ config.enhancement_labels = "enhancement,Enhancement,New Feature,Feature".split(",")
+ config.bug_labels = "bug,Bug,Improvement,Upstream Bug".split(",")
+ config.exclude_labels = "duplicate,question,invalid,wontfix,no_changelog,Exclude From Changelog,Question,Discussion".split(",")
+ end
+rescue LoadError
+ puts "github_changelog_generator is not available. gem install github_changelog_generator to generate changelogs"
+end
diff --git a/bin/chef-zero b/bin/chef-zero
index 54739bc..33fc0e1 100755
--- a/bin/chef-zero
+++ b/bin/chef-zero
@@ -31,7 +31,8 @@ OptionParser.new do |opts|
opts.banner = "Usage: chef-zero [ARGS]"
opts.on("-H", "--host HOST", "Host to bind to (default: 127.0.0.1)") do |value|
- options[:host] = value
+ options[:host] ||= []
+ options[:host] << value
end
opts.on("-p", "--port PORT", "Port to listen on (e.g. 8889, or 8500-8600 or 8885,8888)") do |value|
@@ -55,6 +56,11 @@ OptionParser.new do |opts|
options[:log_file] = value
end
+ opts.on("--enterprise", "Whether to run in enterprise mode") do |value|
+ options[:single_org] = nil
+ options[:osc_compat] = false
+ end
+
opts.on("--multi-org", "Whether to run in multi-org mode") do |value|
options[:single_org] = nil
end
@@ -93,7 +99,11 @@ if options[:daemon]
Process.daemon(true)
server.start(true)
else
- abort 'Process.daemon requires Ruby >= 1.9'
+ if ENV['OS'] == 'Windows_NT'
+ abort 'Daemonization is not supported on Windows. Running 'start chef-zero' will fork the process.'
+ else
+ abort 'Process.daemon requires Ruby >= 1.9'
+ end
end
else
server.start(true)
diff --git a/chef-zero.gemspec b/chef-zero.gemspec
index 3bb0af6..8114a81 100644
--- a/chef-zero.gemspec
+++ b/chef-zero.gemspec
@@ -8,21 +8,28 @@ Gem::Specification.new do |s|
s.summary = 'Self-contained, easy-setup, fast-start in-memory Chef server for testing and solo setup purposes'
s.description = s.summary
s.author = 'John Keiser'
- s.email = 'jkeiser@opscode.com'
- s.homepage = 'http://www.opscode.com'
+ s.email = 'jkeiser@chef.io'
+ s.homepage = 'http://www.chef.io'
s.license = 'Apache 2.0'
- s.add_dependency 'mixlib-log', '~> 1.3'
- s.add_dependency 'hashie', '~> 2.0'
+ s.required_ruby_version = ">= 2.1.0"
+
+ s.add_dependency 'mixlib-log', '~> 1.3'
+ s.add_dependency 'hashie', '>= 2.0', '< 4.0'
s.add_dependency 'uuidtools', '~> 2.1'
s.add_dependency 'ffi-yajl', '~> 2.2'
- s.add_dependency 'rack'
+ s.add_dependency 'rack', '< 2' # 2.0 requires Ruby 2.2+
+ s.add_development_dependency 'pry'
+ s.add_development_dependency 'pry-byebug'
+ s.add_development_dependency 'pry-stack_explorer'
s.add_development_dependency 'rake'
s.add_development_dependency 'rspec'
+ s.add_development_dependency 'chef'
s.bindir = 'bin'
s.executables = ['chef-zero']
s.require_path = 'lib'
- s.files = %w(LICENSE README.md Rakefile) + Dir.glob('{lib,spec}/**/*')
+ s.files = %w(LICENSE README.md Gemfile Rakefile) + Dir.glob('*.gemspec') +
+ Dir.glob('{lib,spec}/**/*', File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
end
diff --git a/gemfiles/berkshelf.gemfile b/gemfiles/berkshelf.gemfile
deleted file mode 100644
index ca99e91..0000000
--- a/gemfiles/berkshelf.gemfile
+++ /dev/null
@@ -1,15 +0,0 @@
-source 'https://rubygems.org'
-
-gemspec :path => "../"
-
-gem 'berkshelf', :github => 'berkshelf'
-
-# development dependencies of berkshelf
-gem 'aruba', '~> 0.5'
-gem 'fuubar', '~> 1.1'
-gem 'rake', '~> 0.9'
-gem 'rspec', '~> 2.13'
-gem 'spork', '~> 0.9'
-gem 'test-kitchen', '~> 1.2'
-gem 'webmock', '~> 1.11'
-gem 'yard', '~> 0.8'
diff --git a/gemfiles/latest-chef.gemfile b/gemfiles/latest-chef.gemfile
deleted file mode 100644
index 47420d6..0000000
--- a/gemfiles/latest-chef.gemfile
+++ /dev/null
@@ -1,5 +0,0 @@
-source 'https://rubygems.org'
-
-gemspec :path => "../"
-
-gem 'chef', :github => 'chef/chef'
diff --git a/gemfiles/no-pedant.gemfile b/gemfiles/no-pedant.gemfile
deleted file mode 100644
index 7784f18..0000000
--- a/gemfiles/no-pedant.gemfile
+++ /dev/null
@@ -1,4 +0,0 @@
-source 'https://rubygems.org'
-
-gemspec :path => "../"
-
diff --git a/gemfiles/oc-chef-pedant.gemfile b/gemfiles/oc-chef-pedant.gemfile
deleted file mode 100644
index dcdee27..0000000
--- a/gemfiles/oc-chef-pedant.gemfile
+++ /dev/null
@@ -1,6 +0,0 @@
-source 'https://rubygems.org'
-gemspec :path => '../'
-
-gem 'rest-client', :github => 'chef/rest-client', :branch => 'lcg/1.6.7-version-lying'
-gem 'oc-chef-pedant', :github => 'chef/oc-chef-pedant', :tag => '2.0.0'
-gem 'chef', :github => 'chef/chef'
diff --git a/lib/chef_zero.rb b/lib/chef_zero.rb
index 354ab07..94633d4 100644
--- a/lib/chef_zero.rb
+++ b/lib/chef_zero.rb
@@ -1,6 +1,9 @@
module ChefZero
require 'chef_zero/log'
+ MIN_API_VERSION = 0
+ MAX_API_VERSION = 1
+
CERTIFICATE = "-----BEGIN CERTIFICATE-----\nMIIDMzCCApygAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnjELMAkGA1UEBhMCVVMx\nEzARBgNVBAgMCldhc2hpbmd0b24xEDAOBgNVBAcMB1NlYXR0bGUxFjAUBgNVBAoM\nDU9wc2NvZGUsIEluYy4xHDAaBgNVBAsME0NlcnRpZmljYXRlIFNlcnZpY2UxMjAw\nBgNVBAMMKW9wc2NvZGUuY29tL2VtYWlsQWRkcmVzcz1hdXRoQG9wc2NvZGUuY29t\nMB4XDTEyMTEyMTAwMzQyMVoXDTIyMTExOTAwMzQyMVowgZsxEDAOBgNVBAcTB1Nl\nYXR0bGUxEzARBgNVBAgTCldhc2hpbmd0b24xCzAJBgNVBAYTAlVTMRwwGgYDVQQL\nExNDZXJ0aWZpY2F0ZSBTZXJ2aWNlMRYwFAYDVQQKEw1PcHNjb2RlLCBJbmMuMS8w\nLQYDVQQDFCZVUkk6aHR0cDovL29wc2NvZGUuY29tL0dVSURTL3VzZXJfZ3VpZDCC\nASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANLDmPbR71bS2esZlZh/HfC6\n0azXFjl2677wq2ovk9xrUb0Ui4ZLC66TqQ9C/RBzOjXU4TRf3hgPTqvlCgHusl0d\nIcLCrsSl6kPEhJpYWWfRoroIAwf82A9yLQekhqXZEXu5EKkwoUMqyF6m0ZCasaE1\ny8niQxdLAsk3ady/CGQlFqHTPKFfU5UASR2LRtYC1MCIvJHDFRKAp9kPJbQo9P37\nZ8IU7cDudkZFgNLmDixlWsh7C0ghX8fgAlj1P6FgsFufygam973k79GhIP54dELB\nc0S6E8ekkRSOXU9jX/IoiXuFglBvFihAdhvED58bMXzj2AwXUyeAlxItnvs+NVUC\nAwEAATANBgkqhkiG9w0BAQUFAAOBgQBkFZRbMoywK3hb0/X7MXmPYa7nlfnd5UXq\nr2n32ettzZNmEPaI2d1j+//nL5qqhOlrWPS88eKEPnBOX/jZpUWOuAAddnrvFzgw\nrp/C2H7oMT+29F+5ezeViLKbzoFYb4yECHBoi66IFXNae13yj7taMboBeUmE664G\nTB/MZpRr8g==\n-----END CERTIFICATE-----\n"
PUBLIC_KEY = "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0sOY9tHvVtLZ6xmVmH8d\n8LrRrNcWOXbrvvCrai+T3GtRvRSLhksLrpOpD0L9EHM6NdThNF/eGA9Oq+UKAe6y\nXR0hwsKuxKXqQ8SEmlhZZ9GiuggDB/zYD3ItB6SGpdkRe7kQqTChQyrIXqbRkJqx\noTXLyeJDF0sCyTdp3L8IZCUWodM8oV9TlQBJHYtG1gLUwIi8kcMVEoCn2Q8ltCj0\n/ftnwhTtwO52RkWA0uYOLGVayHsLSCFfx+ACWPU/oWCwW5/KBqb3veTv0aEg/nh0\nQsFzRLoTx6SRFI5dT2Nf8iiJe4WCUG8WKEB2G8QPnxsxfOPYDBdTJ4CXEi2e+z41\nVQIDAQAB\n-----END PUBLIC KEY-----\n"
PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA0sOY9tHvVtLZ6xmVmH8d8LrRrNcWOXbrvvCrai+T3GtRvRSL\nhksLrpOpD0L9EHM6NdThNF/eGA9Oq+UKAe6yXR0hwsKuxKXqQ8SEmlhZZ9GiuggD\nB/zYD3ItB6SGpdkRe7kQqTChQyrIXqbRkJqxoTXLyeJDF0sCyTdp3L8IZCUWodM8\noV9TlQBJHYtG1gLUwIi8kcMVEoCn2Q8ltCj0/ftnwhTtwO52RkWA0uYOLGVayHsL\nSCFfx+ACWPU/oWCwW5/KBqb3veTv0aEg/nh0QsFzRLoTx6SRFI5dT2Nf8iiJe4WC\nUG8WKEB2G8QPnxsxfOPYDBdTJ4CXEi2e+z41VQIDAQABAoIBAALhqbW2KQ+G0nPk\nZacwFbi01SkHx8YBWjfCEpXhEKRy0ytCnKW5YO+CFU2gHNWcva7+uhV9OgwaKXkw\nKHLeUJH1VADVqI4Htqw2g5mYm6BPvWnNsjzpuAp+BR+VoEGkNhj67r9hatMAQr0I\nitTvSH5rvd2EumYXIHKfz1K1SegUk1u1EL1RcMzRmZe4gDb6eNBs9Sg4im4ybTG6\npPIytA8vBQVWhjuAR2Tm+wZHiy0Az6Vu7c2mS07FSX6FO4E8SxWf8idaK9ijMGSq\nFvIS04mrY6XCPUPUC4qm1qNnhDPpOr7CpI2OO98SqGanStS5NFlSFXeXPpM280/u\nfZUA0AECgYEA+x7QUnffDrt7LK2cX6wbvn4mRnFxet7bJjrfWIHf+Rm0URikaNma\nh0/wNKpKBwIH+eHK/LslgzcplrqPytGGHLOG97Gyo5tGAzyLHUWBmsNkRksY2sPL\nuHq6pYWJNkqhnWGnIbmqCr0EWih82x/y4qxbJYpYqXMrit0wVf7yAgkCgYEA1twI\ngFaXqesetTPoEHSQSgC8S4D5/NkdriUXCYb06REcvo9IpFMuiOkVUYNN5d3MDNTP\nIdBicfmvfNELvBtXDomEUD8ls1UuoTIXRNGZ0VsZXu7OErXCK0JKNNyqRmOwcvYL\nJRqLfnlei5Ndo1lu286yL74c5rdTLs/nI2p4e+0CgYB079ZmcLeILrmfBoFI8+Y/\ngJLmPrFvXBOE6+lRV7kqUFPtZ6I3yQzyccETZTDvrnx0WjaiFavUPH27WMjY01S2\nTMtO0Iq1MPsbSrglO1as8MvjB9ldFcvp7gy4Q0Sv6XT0yqJ/S+vo8Df0m+H4UBpU\nf5o6EwBSd/UQxwtZIE0lsQKBgQCswfjX8Eg8KL/lJNpIOOE3j4XXE9ptksmJl2sB\njxDnQYoiMqVO808saHVquC/vTrpd6tKtNpehWwjeTFuqITWLi8jmmQ+gNTKsC9Gn\n1Pxf2Gb67PqnEpwQGln+TRtgQ5HBrdHiQIi+5am+gnw89pDrjjO5rZwhanAo6KPJ\n1zcPNQKBgQDxFu8v4frDmRNCVaZS4f1B6wTrcMrnibIDlnzrK9GG6Hz1U7dDv8s8\nNf4UmeMzDXjlPWZVOvS5+9HKJPdPj7/onv8B2m18+lcgTTDJBkza7R1mjL1Cje/Z\nKcVGsryKN6cjE7yCDasnA7R2rVBV/7NWeJV77bmzT5O//rW4yIfUIg==\n-----END RSA PRIVATE KEY-----\n"
diff --git a/lib/chef_zero/chef_data/acl_path.rb b/lib/chef_zero/chef_data/acl_path.rb
index c783a1a..52b43d4 100644
--- a/lib/chef_zero/chef_data/acl_path.rb
+++ b/lib/chef_zero/chef_data/acl_path.rb
@@ -13,7 +13,8 @@ module ChefZero
# specified on X, they are not inherited from X's parent
# - stop adding pivotal to acls (he already has access to what he needs)
module AclPath
- ORG_DATA_TYPES = %w(clients cookbooks containers data environments groups nodes roles sandboxes)
+ ORG_DATA_TYPES = %w(clients cookbook_artifacts cookbooks containers data environments groups
+ nodes policies policy_groups roles sandboxes)
TOP_DATA_TYPES = %w(containers organizations users)
# ACL data paths for a partition are:
diff --git a/lib/chef_zero/chef_data/cookbook_data.rb b/lib/chef_zero/chef_data/cookbook_data.rb
index aed625b..e690fde 100644
--- a/lib/chef_zero/chef_data/cookbook_data.rb
+++ b/lib/chef_zero/chef_data/cookbook_data.rb
@@ -108,6 +108,11 @@ module ChefZero
cookbook_arg(:replacing, cookbook, version_constraints)
end
+ def gem(*opts)
+ self[:gems] ||= []
+ self[:gems] << opts
+ end
+
def recipe(recipe, description)
self[:recipes][recipe] = description
end
diff --git a/lib/chef_zero/chef_data/data_normalizer.rb b/lib/chef_zero/chef_data/data_normalizer.rb
index 404db03..e819f1d 100644
--- a/lib/chef_zero/chef_data/data_normalizer.rb
+++ b/lib/chef_zero/chef_data/data_normalizer.rb
@@ -8,7 +8,7 @@ module ChefZero
def self.normalize_acls(acls)
ChefData::DefaultCreator::PERMISSIONS.each do |perm|
acls[perm] ||= {}
- acls[perm]['actors'] ||= []
+ (acls[perm]['actors'] ||= []).uniq! # this gets doubled sometimes, for reasons.
acls[perm]['groups'] ||= []
end
acls
@@ -17,7 +17,8 @@ module ChefZero
def self.normalize_client(client, name, orgname = nil)
client['name'] ||= name
client['clientname'] ||= name
- client['public_key'] ||= PUBLIC_KEY
+ client['admin'] = !!client['admin'] if client.key?('admin')
+ client['public_key'] = PUBLIC_KEY unless client.key?('public_key')
client['orgname'] ||= orgname
client['validator'] ||= false
client['validator'] = !!client['validator']
@@ -35,7 +36,7 @@ module ChefZero
def self.normalize_user(user, name, identity_keys, osc_compat, method=nil)
user[identity_keys.first] ||= name
- user['public_key'] ||= PUBLIC_KEY
+ user['public_key'] = PUBLIC_KEY unless user.key?('public_key')
user['admin'] ||= false
user['admin'] = !!user['admin']
user['openid'] ||= nil
@@ -78,7 +79,8 @@ module ChefZero
data_bag_item
end
- def self.normalize_cookbook(endpoint, org_prefix, cookbook, name, version, base_uri, method)
+ def self.normalize_cookbook(endpoint, org_prefix, cookbook, name, version, base_uri, method,
+ is_cookbook_artifact=false)
# TODO I feel dirty
if method != 'PUT'
cookbook.each_pair do |key, value|
@@ -91,24 +93,28 @@ module ChefZero
end
end
cookbook['name'] ||= "#{name}-#{version}"
- # TODO this feels wrong, but the real chef server doesn't expand this default
- # cookbook['version'] ||= version
- cookbook['cookbook_name'] ||= name
+ # TODO it feels wrong, but the real chef server doesn't expand 'version', so we don't either.
+
cookbook['frozen?'] ||= false
cookbook['metadata'] ||= {}
cookbook['metadata']['version'] ||= version
- # Sad to not be expanding defaults just because Chef doesn't :(
- # cookbook['metadata']['name'] ||= name
- # cookbook['metadata']['description'] ||= "A fabulous new cookbook"
+
+ # defaults set by the client and not the Server:
+ # metadata[name, description, maintainer, maintainer_email, license]
+
cookbook['metadata']['long_description'] ||= ""
- # cookbook['metadata']['maintainer'] ||= "YOUR_COMPANY_NAME"
- # cookbook['metadata']['maintainer_email'] ||= "YOUR_EMAIL"
- # cookbook['metadata']['license'] ||= "none"
cookbook['metadata']['dependencies'] ||= {}
cookbook['metadata']['attributes'] ||= {}
cookbook['metadata']['recipes'] ||= {}
end
- cookbook['json_class'] ||= 'Chef::CookbookVersion'
+
+ if is_cookbook_artifact
+ cookbook.delete('json_class')
+ else
+ cookbook['cookbook_name'] ||= name
+ cookbook['json_class'] ||= 'Chef::CookbookVersion'
+ end
+
cookbook['chef_type'] ||= 'cookbook_version'
if method == 'MIN'
cookbook['metadata'].delete('attributes')
@@ -157,7 +163,7 @@ module ChefZero
node['chef_type'] ||= 'node'
node['chef_environment'] ||= '_default'
node['override'] ||= {}
- node['normal'] ||= {}
+ node['normal'] ||= {"tags" => []}
node['default'] ||= {}
node['automatic'] ||= {}
node['run_list'] ||= []
@@ -165,6 +171,20 @@ module ChefZero
node
end
+ def self.normalize_policy(policy, name, revision)
+ policy['name'] ||= name
+ policy['revision_id'] ||= revision
+ policy['run_list'] ||= []
+ policy['cookbook_locks'] ||= {}
+ policy
+ end
+
+ def self.normalize_policy_group(policy_group, name)
+ policy_group[name] ||= 'name'
+ policy_group['policies'] ||= {}
+ policy_group
+ end
+
def self.normalize_organization(org, name)
org['name'] ||= name
org['full_name'] ||= name
diff --git a/lib/chef_zero/chef_data/default_creator.rb b/lib/chef_zero/chef_data/default_creator.rb
index d1a0118..957018c 100644
--- a/lib/chef_zero/chef_data/default_creator.rb
+++ b/lib/chef_zero/chef_data/default_creator.rb
@@ -148,6 +148,7 @@ module ChefZero
DEFAULT_ORG_SPINE = {
'clients' => {},
+ 'cookbook_artifacts' => {},
'cookbooks' => {},
'data' => {},
'environments' => %w(_default),
@@ -155,12 +156,14 @@ module ChefZero
'checksums' => {}
},
'nodes' => {},
+ 'policies' => {},
+ 'policy_groups' => {},
'roles' => {},
'sandboxes' => {},
'users' => {},
'org' => {},
- 'containers' => %w(clients containers cookbooks data environments groups nodes roles sandboxes policies cookbook_artifacts),
+ 'containers' => %w(clients containers cookbook_artifacts cookbooks data environments groups nodes policies policy_groups roles sandboxes),
'groups' => %w(admins billing-admins clients users),
'association_requests' => {}
}
@@ -279,7 +282,7 @@ module ChefZero
'delete' => { 'groups' => %w(admins) },
'grant' => { 'groups' => %w(admins) },
}
- when 'containers/cookbooks', 'containers/environments', 'containers/roles'
+ when 'containers/environments', 'containers/roles', 'containers/policy_groups', 'containers/policies'
{
'create' => { 'groups' => %w(admins users) },
'read' => { 'groups' => %w(admins users clients) },
@@ -287,7 +290,7 @@ module ChefZero
'delete' => { 'groups' => %w(admins users) },
'grant' => { 'groups' => %w(admins) },
}
- when 'containers/cookbooks', 'containers/data'
+ when 'containers/cookbooks', 'containers/cookbook_artifacts', 'containers/data'
{
'create' => { 'groups' => %w(admins users clients) },
'read' => { 'groups' => %w(admins users clients) },
@@ -378,11 +381,12 @@ module ChefZero
# Non-default containers do not get superusers added to them,
# because reasons.
unless path.size == 4 && path[0] == 'organizations' && path[2] == 'containers' && !exists?(path)
- owners |= superusers
+ owners += superusers
end
end
- owners.uniq
+ # we don't de-dup this list, because pedant expects to see ["pivotal", "pivotal"] in some cases.
+ owners
end
def default_acl(acl_path, acl={})
@@ -436,7 +440,7 @@ module ChefZero
when 4
return path[0] == 'organizations' && (
(path[2] == 'acls' && path[1] != 'root') ||
- %w(cookbooks data).include?(path[2]))
+ %w(cookbooks cookbook_artifacts data policies policy_groups).include?(path[2]))
else
return false
end
diff --git a/lib/chef_zero/data_store/data_error.rb b/lib/chef_zero/data_store/data_error.rb
index 9822a6b..b392e58 100644
--- a/lib/chef_zero/data_store/data_error.rb
+++ b/lib/chef_zero/data_store/data_error.rb
@@ -19,13 +19,14 @@
module ChefZero
module DataStore
class DataError < StandardError
+ attr_reader :path, :cause
+
def initialize(path, cause = nil)
@path = path
@cause = cause
+ path_for_msg = path.nil? ? "nil" : "/#{path.join('/')}"
+ super "Data path: #{path_for_msg}"
end
-
- attr_reader :path
- attr_reader :cause
end
end
end
diff --git a/lib/chef_zero/data_store/default_facade.rb b/lib/chef_zero/data_store/default_facade.rb
index 0d4cf34..c941322 100644
--- a/lib/chef_zero/data_store/default_facade.rb
+++ b/lib/chef_zero/data_store/default_facade.rb
@@ -62,7 +62,7 @@ module ChefZero
end
options_hash = options.last.is_a?(Hash) ? options.last : {}
- default_creator.created(path + [ name ], options_hash[:requestor], options.include?(:recursive))
+ default_creator.created(path + [ name ], options_hash[:requestor], options.include?(:create_dir))
end
def get(path, request=nil)
diff --git a/lib/chef_zero/endpoints/actor_default_key_endpoint.rb b/lib/chef_zero/endpoints/actor_default_key_endpoint.rb
new file mode 100644
index 0000000..3be1475
--- /dev/null
+++ b/lib/chef_zero/endpoints/actor_default_key_endpoint.rb
@@ -0,0 +1,77 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # ActorDefaultKeyEndpoint
+ #
+ # This class handles DELETE/GET/PUT requests for client/user default public
+ # keys, i.e. requests with identity key "default". All others are handled
+ # by ActorKeyEndpoint.
+ #
+ # Default public keys are stored with the actor (client or user) instead of
+ # under user/client_keys. Handling those in a separate endpoint offloads
+ # the branching logic onto the router rather than branching in every
+ # endpoint method (`if request.rest_path[-1] == "default" ...`).
+ #
+ # /users/USER/keys/default
+ # /organizations/ORG/clients/CLIENT/keys/default
+ class ActorDefaultKeyEndpoint < RestBase
+ DEFAULT_PUBLIC_KEY_NAME = "default".freeze
+
+ def get(request)
+ # 404 if actor doesn't exist
+ actor_data = get_actor_data(request)
+ key_data = default_public_key_from_actor(actor_data)
+
+ # 404 if the actor doesn't have a default key
+ if key_data["public_key"].nil?
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
+ end
+
+ json_response(200, default_public_key_from_actor(actor_data))
+ end
+
+ def delete(request)
+ path = actor_path(request)
+ actor_data = get_actor_data(request) # 404 if actor doesn't exist
+
+ default_public_key = delete_actor_default_public_key!(request, path, actor_data)
+ json_response(200, default_public_key)
+ end
+
+ def put(request)
+ # 404 if actor doesn't exist
+ actor_data = get_actor_data(request)
+
+ new_public_key = parse_json(request.body)["public_key"]
+ actor_data["public_key"] = new_public_key
+
+ set_data(request, actor_path(request), to_json(actor_data))
+ end
+
+ private
+
+ def actor_path(request)
+ return request.rest_path[0..3] if request.rest_path[2] == "clients"
+ request.rest_path[0..1]
+ end
+
+ def get_actor_data(request)
+ path = actor_path(request)
+ parse_json(get_data(request, path))
+ end
+
+ def default_public_key_from_actor(actor_data)
+ { "name" => DEFAULT_PUBLIC_KEY_NAME,
+ "public_key" => actor_data["public_key"],
+ "expiration_date" => "infinity" }
+ end
+
+ def delete_actor_default_public_key!(request, path, actor_data)
+ new_actor_data = actor_data.merge("public_key" => nil)
+ set_data(request, path, to_json(new_actor_data))
+ default_public_key_from_actor(actor_data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/actor_endpoint.rb b/lib/chef_zero/endpoints/actor_endpoint.rb
index 1572ac1..dd2caf2 100644
--- a/lib/chef_zero/endpoints/actor_endpoint.rb
+++ b/lib/chef_zero/endpoints/actor_endpoint.rb
@@ -8,8 +8,19 @@ module ChefZero
# /organizations/ORG/users/NAME
# /users/NAME
class ActorEndpoint < RestObjectEndpoint
+
+ def get(request)
+ result = super
+ user_data = parse_json(result[2])
+
+ user_data.delete("public_key") unless request.api_v0?
+
+ json_response(200, user_data)
+ end
+
def delete(request)
result = super
+
if request.rest_path[0] == 'users'
list_data(request, [ 'organizations' ]).each do |org|
begin
@@ -18,12 +29,15 @@ module ChefZero
end
end
end
+
+ delete_actor_keys!(request)
result
end
def put(request)
# Find out if we're updating the public key.
request_body = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
+
if request_body['public_key'].nil?
# If public_key is null, then don't overwrite it. Weird patchiness.
body_modified = true
@@ -33,18 +47,18 @@ module ChefZero
end
# Generate private_key if requested.
- if request_body.has_key?('private_key')
+ if request_body.key?('private_key')
body_modified = true
- if request_body['private_key']
+
+ if request_body.delete('private_key')
private_key, public_key = server.gen_key_pair
updating_public_key = true
request_body['public_key'] = public_key
end
- request_body.delete('private_key')
end
- # Save request
- request.body = FFI_Yajl::Encoder.encode(request_body, :pretty => true) if body_modified
+ # Put modified body back in `request.body`
+ request.body = to_json(request_body) if body_modified
# PUT /clients is patchy
request.body = patch_request_body(request)
@@ -53,27 +67,29 @@ module ChefZero
# Inject private_key into response, delete public_key/password if applicable
if result[0] == 200 || result[0] == 201
+ client_or_user_name = identity_key_value(request) || request.rest_path[-1]
+
+ if is_rename?(request)
+ rename_keys!(request, client_or_user_name)
+ end
+
if request.rest_path[0] == 'users'
- key = nil
- identity_keys.each do |identity_key|
- key ||= request_body[identity_key]
- end
- key ||= request.rest_path[-1]
response = {
- 'uri' => build_uri(request.base_uri, [ 'users', key ])
+ 'uri' => build_uri(request.base_uri, [ 'users', client_or_user_name ])
}
else
- response = FFI_Yajl::Parser.parse(result[2], :create_additions => false)
+ response = parse_json(result[2])
end
- if request.rest_path[2] == 'clients'
+ if client?(request)
response['private_key'] = private_key ? private_key : false
else
response['private_key'] = private_key if private_key
+ response.delete('public_key') unless updating_public_key
end
- response.delete('public_key') if !updating_public_key && request.rest_path[2] == 'users'
response.delete('password')
+
json_response(result[0], response)
else
result
@@ -81,13 +97,87 @@ module ChefZero
end
def populate_defaults(request, response_json)
- response = FFI_Yajl::Parser.parse(response_json, :create_additions => false)
- if request.rest_path[2] == 'clients'
- response = ChefData::DataNormalizer.normalize_client(response,request.rest_path[3], request.rest_path[1])
+ response = parse_json(response_json)
+
+ populated_response =
+ if client?(request)
+ ChefData::DataNormalizer.normalize_client(
+ response,
+ response["name"] || request.rest_path[-1],
+ request.rest_path[1]
+ )
+ else
+ ChefData::DataNormalizer.normalize_user(
+ response,
+ response["username"] || request.rest_path[-1],
+ identity_keys,
+ server.options[:osc_compat],
+ request.method
+ )
+ end
+
+ to_json(populated_response)
+ end
+
+ private
+
+ # Move key data to new path
+ def rename_keys!(request, new_client_or_user_name)
+ orig_keys_path = keys_path_base(request)
+ new_keys_path = orig_keys_path.dup
+ .tap {|path| path[-2] = new_client_or_user_name }
+
+ key_names = list_data_or_else(request, orig_keys_path, nil)
+ return unless key_names # No keys to move
+
+ key_names.each do |key_name|
+ # Get old data
+ orig_path = [ *orig_keys_path, key_name ]
+ data = get_data(request, orig_path, :data_store_exceptions)
+
+ # Copy data to new path
+ create_data(
+ request,
+ new_keys_path, key_name,
+ data,
+ :create_dir
+ )
+ end
+
+ # Delete original data
+ delete_data_dir(request, orig_keys_path, :recursive, :data_store_exceptions)
+ end
+
+ def delete_actor_keys!(request)
+ path = keys_path_base(request)[0..-2]
+ delete_data_dir(request, path, :recursive, :data_store_exceptions)
+ rescue DataStore::DataNotFoundError
+ end
+
+ def client?(request, rest_path=nil)
+ rest_path ||= request.rest_path
+ request.rest_path[2] == "clients"
+ end
+
+ # Return the data store keys path for the request client or user, e.g.
+ #
+ # /organizations/ORG/clients/CLIENT -> /organizations/ORG/client_keys/CLIENT/keys
+ # /organizations/ORG/users/USER -> /organizations/ORG/user_keys/USER/keys
+ # /users/USER -> /user_keys/USER
+ #
+ def keys_path_base(request, client_or_user_name=nil)
+ rest_path = (rest_path || request.rest_path).dup
+ rest_path = rest_path.dup
+ case rest_path[-2]
+ when "users"
+ rest_path[-2] = "user_keys"
+ when "clients"
+ rest_path[-2] = "client_keys"
else
- response = ChefData::DataNormalizer.normalize_user(response, request.rest_path[3], identity_keys, server.options[:osc_compat], request.method)
+ raise "Unexpected URL #{rest_path.join("/")}: cannot determine key path"
end
- FFI_Yajl::Encoder.encode(response, :pretty => true)
+ rest_path << "keys"
+ rest_path
end
end
end
diff --git a/lib/chef_zero/endpoints/actor_key_endpoint.rb b/lib/chef_zero/endpoints/actor_key_endpoint.rb
new file mode 100644
index 0000000..f2b65ed
--- /dev/null
+++ b/lib/chef_zero/endpoints/actor_key_endpoint.rb
@@ -0,0 +1,62 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # ActorKeyEndpoint
+ #
+ # This class handles DELETE/GET/PUT requests for all client/user keys
+ # **except** default public keys, i.e. requests with identity key
+ # "default". Those are handled by ActorDefaultKeyEndpoint. See that class
+ # for more information.
+ #
+ # /users/USER/keys/NAME
+ # /organizations/ORG/clients/CLIENT/keys/NAME
+ class ActorKeyEndpoint < RestBase
+ def get(request)
+ validate_actor!(request)
+ key_path = data_path(request)
+ already_json_response(200, get_data(request, key_path))
+ end
+
+ def delete(request)
+ validate_actor!(request) # 404 if actor doesn't exist
+
+ key_path = data_path(request)
+ data = get_data(request, key_path)
+ delete_data(request, key_path)
+
+ already_json_response(200, data)
+ end
+
+ def put(request)
+ validate_actor!(request) # 404 if actor doesn't exist
+ set_data(request, data_path(request), request.body)
+ end
+
+ private
+
+ # Returns the keys data store path, which is the same as
+ # `request.rest_path` except with "client_keys" instead of "clients" or
+ # "user_keys" instead of "users."
+ def data_path(request)
+ request.rest_path.dup.tap do |path|
+ if client?(request)
+ path[2] = "client_keys"
+ else
+ path[0] = "user_keys"
+ end
+ end
+ end
+
+ # Raises RestErrorResponse (404) if actor doesn't exist
+ def validate_actor!(request)
+ actor_path = request.rest_path[ client?(request) ? 0..3 : 0..1 ]
+ get_data(request, actor_path)
+ end
+
+ def client?(request)
+ request.rest_path[2] == "clients"
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/actor_keys_endpoint.rb b/lib/chef_zero/endpoints/actor_keys_endpoint.rb
new file mode 100644
index 0000000..f3624d6
--- /dev/null
+++ b/lib/chef_zero/endpoints/actor_keys_endpoint.rb
@@ -0,0 +1,129 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # /users/USER/keys
+ # /organizations/ORG/clients/CLIENT/keys
+ class ActorKeysEndpoint < RestBase
+ DEFAULT_PUBLIC_KEY_NAME = "default"
+ DATE_FORMAT = "%FT%TZ" # e.g. 2015-12-24T21:00:00Z
+
+ def get(request, alt_uri_root=nil)
+ path = data_path(request)
+
+ # Get actor or 404 if it doesn't exist
+ actor_json = get_data(request, actor_path(request))
+
+ key_names = list_data_or_else(request, path, [])
+ key_names.unshift(DEFAULT_PUBLIC_KEY_NAME) if actor_has_default_public_key?(actor_json)
+
+ result = key_names.map do |key_name|
+ list_key(request, [ *path, key_name ], alt_uri_root)
+ end
+
+ json_response(200, result)
+ end
+
+ def post(request)
+ request_body = parse_json(request.body)
+
+ # Try loading the client or user so a 404 is returned if it doesn't exist
+ actor_json = get_data(request, actor_path(request))
+
+ generate_keys = request_body["public_key"].nil?
+
+ if generate_keys
+ private_key, public_key = server.gen_key_pair
+ else
+ public_key = request_body['public_key']
+ end
+
+ key_name = request_body["name"]
+
+ if key_name == DEFAULT_PUBLIC_KEY_NAME
+ store_actor_default_public_key!(request, actor_json, public_key)
+ else
+ store_actor_public_key!(request, key_name, public_key, request_body["expiration_date"])
+ end
+
+ response_body = { "uri" => key_uri(request, key_name) }
+ response_body["private_key"] = private_key if generate_keys
+
+ json_response(201, response_body,
+ headers: { "Location" => response_body["uri"] })
+ end
+
+ private
+
+ def store_actor_public_key!(request, name, public_key, expiration_date)
+ data = to_json(
+ "name" => name,
+ "public_key" => public_key,
+ "expiration_date" => expiration_date
+ )
+
+ create_data(request, data_path(request), name, data, :create_dir)
+ end
+
+ def store_actor_default_public_key!(request, actor_json, public_key)
+ actor_data = parse_json(actor_json)
+
+ if actor_data["public_key"]
+ raise RestErrorResponse.new(409, "Object already exists: #{key_uri(request, DEFAULT_PUBLIC_KEY_NAME)}")
+ end
+
+ actor_data["public_key"] = public_key
+ set_data(request, actor_path(request), to_json(actor_data))
+
+ end
+
+ # Returns the keys data store path, which is the same as
+ # `request.rest_path` except with "user_keys" instead of "users" or
+ # "client_keys" instead of "clients."
+ def data_path(request)
+ request.rest_path.dup.tap do |path|
+ if client?(request)
+ path[2] = "client_keys"
+ else
+ path[0] = "user_keys"
+ end
+ end
+ end
+
+ def list_key(request, data_path, alt_uri_root=nil)
+ key_name, expiration_date =
+ if data_path[-1] == DEFAULT_PUBLIC_KEY_NAME
+ [ DEFAULT_PUBLIC_KEY_NAME, "infinity" ]
+ else
+ parse_json(get_data(request, data_path))
+ .values_at("name", "expiration_date")
+ end
+
+ expired = expiration_date != "infinity" &&
+ DateTime.now > DateTime.strptime(expiration_date, DATE_FORMAT)
+
+ { "name" => key_name,
+ "uri" => key_uri(request, key_name, alt_uri_root),
+ "expired" => expired }
+ end
+
+ def client?(request)
+ request.rest_path[2] == "clients"
+ end
+
+ def key_uri(request, key_name, alt_uri_root=nil)
+ uri_root = alt_uri_root.nil? ? request.rest_path : alt_uri_root
+ build_uri(request.base_uri, [ *uri_root, key_name ])
+ end
+
+ def actor_path(request)
+ return request.rest_path[0..3] if client?(request)
+ request.rest_path[0..1]
+ end
+
+ def actor_has_default_public_key?(actor_json)
+ !!parse_json(actor_json)["public_key"]
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/actors_endpoint.rb b/lib/chef_zero/endpoints/actors_endpoint.rb
index c6c676f..6297aed 100644
--- a/lib/chef_zero/endpoints/actors_endpoint.rb
+++ b/lib/chef_zero/endpoints/actors_endpoint.rb
@@ -8,30 +8,25 @@ module ChefZero
def get(request)
response = super(request)
- if request.query_params['email']
- results = FFI_Yajl::Parser.parse(response[2], :create_additions => false)
- new_results = {}
- results.each do |name, url|
- record = get_data(request, request.rest_path + [ name ], :nil)
- if record
- record = FFI_Yajl::Parser.parse(record, :create_additions => false)
- new_results[name] = url if record['email'] == request.query_params['email']
- end
- end
- response[2] = FFI_Yajl::Encoder.encode(new_results, :pretty => true)
+ # apply query filters: if one applies, stop processing rest
+ # (precendence matches chef-server: https://github.com/chef/chef-server/blob/268a0c9/src/oc_erchef/apps/chef_objects/src/chef_user.erl#L554-L559)
+ if value = request.query_params['external_authentication_uid']
+ response[2] = filter('external_authentication_uid', value, request, response[2])
+ elsif value = request.query_params['email']
+ response[2] = filter('email', value, request, response[2])
end
if request.query_params['verbose']
- results = FFI_Yajl::Parser.parse(response[2], :create_additions => false)
+ results = parse_json(response[2])
results.each do |name, url|
record = get_data(request, request.rest_path + [ name ], :nil)
if record
- record = FFI_Yajl::Parser.parse(record, :create_additions => false)
+ record = parse_json(record)
record = ChefData::DataNormalizer.normalize_user(record, name, identity_keys, server.options[:osc_compat])
results[name] = record
end
end
- response[2] = FFI_Yajl::Encoder.encode(results, :pretty => true)
+ response[2] = to_json(results)
end
response
end
@@ -39,26 +34,67 @@ module ChefZero
def post(request)
# First, find out if the user actually posted a public key. If not, make
# one.
- request_body = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
+ request_body = parse_json(request.body)
public_key = request_body['public_key']
- if !public_key
+
+ skip_key_create = !request.api_v0? && !request_body["create_key"]
+
+ if !public_key && !skip_key_create
private_key, public_key = server.gen_key_pair
request_body['public_key'] = public_key
- request.body = FFI_Yajl::Encoder.encode(request_body, :pretty => true)
+ request.body = to_json(request_body)
+ elsif skip_key_create
+ request_body['public_key'] = nil
+ request.body = to_json(request_body)
end
result = super(request)
if result[0] == 201
# If we generated a key, stuff it in the response.
- response = FFI_Yajl::Parser.parse(result[2], :create_additions => false)
- response['private_key'] = private_key if private_key
- response['public_key'] = public_key unless request.rest_path[0] == 'users'
+ user_data = parse_json(result[2])
+
+ key_data = {}
+ key_data['private_key'] = private_key if private_key
+ key_data['public_key'] = public_key unless request.rest_path[0] == 'users'
+
+ response =
+ if request.api_v0?
+ user_data.merge!(key_data)
+ elsif skip_key_create && !public_key
+ user_data
+ else
+ actor_name = request_body["name"] || request_body["username"] || request_body["clientname"]
+
+ relpath_to_default_key = [ actor_name, "keys", "default" ]
+ key_data["uri"] = build_uri(request.base_uri, request.rest_path + relpath_to_default_key)
+ key_data["public_key"] = public_key
+ key_data["name"] = "default"
+ key_data["expiration_date"] = "infinity"
+ user_data["chef_key"] = key_data
+ user_data
+ end
+
json_response(201, response)
else
result
end
end
+
+ private
+
+ def filter(key, value, request, resp)
+ results = parse_json(resp)
+ new_results = {}
+ results.each do |name, url|
+ record = get_data(request, request.rest_path + [ name ], :nil)
+ if record
+ record = parse_json(record)
+ new_results[name] = url if record[key] == value
+ end
+ end
+ to_json(new_results)
+ end
end
end
end
diff --git a/lib/chef_zero/endpoints/containers_endpoint.rb b/lib/chef_zero/endpoints/containers_endpoint.rb
index 3f7af87..8a4220f 100644
--- a/lib/chef_zero/endpoints/containers_endpoint.rb
+++ b/lib/chef_zero/endpoints/containers_endpoint.rb
@@ -8,6 +8,18 @@ module ChefZero
def initialize(server)
super(server, %w(id containername))
end
+
+ # create a container.
+ # input: {"containername"=>"new-container", "containerpath"=>"/"}
+ def post(request)
+ data = parse_json(request.body)
+ # if they don't match, id wins.
+ container_name = data["id"] || data["containername"]
+ container_path_suffix = data["containerpath"].split("/").reject { |o| o.empty? }
+ create_data(request, request.rest_path, container_name, to_json({}), :create_dir)
+
+ json_response(201, { uri: build_uri(request.base_uri, request.rest_path + container_path_suffix + [container_name]) })
+ end
end
end
end
diff --git a/lib/chef_zero/endpoints/controls_endpoint.rb b/lib/chef_zero/endpoints/controls_endpoint.rb
new file mode 100644
index 0000000..5790d7f
--- /dev/null
+++ b/lib/chef_zero/endpoints/controls_endpoint.rb
@@ -0,0 +1,15 @@
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/controls
+ class ControlsEndpoint < RestBase
+ # ours is not to wonder why; ours is but to make the pedant specs pass.
+ def get(request)
+ error(410, "Server says 410, chef-zero says 410.")
+ end
+
+ def post(request)
+ error(410, "Server says 410, chef-zero says 410.")
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/cookbook_artifact_endpoint.rb b/lib/chef_zero/endpoints/cookbook_artifact_endpoint.rb
new file mode 100644
index 0000000..e17fea2
--- /dev/null
+++ b/lib/chef_zero/endpoints/cookbook_artifact_endpoint.rb
@@ -0,0 +1,24 @@
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ class CookbookArtifactEndpoint < RestBase
+ # GET /organizations/ORG/cookbook_artifacts/COOKBOOK
+ def get(request)
+ cookbook_name = request.rest_path.last
+ cookbook_url = build_uri(request.base_uri, request.rest_path)
+ response_data = {}
+ versions = []
+
+ list_data(request).each do |identifier|
+ artifact_url = build_uri(request.base_uri, request.rest_path + [cookbook_name, identifier])
+ versions << { url: artifact_url, identifier: identifier }
+ end
+
+ response_data[cookbook_name] = { url: cookbook_url, versions: versions }
+
+ return json_response(200, response_data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/cookbook_artifact_identifier_endpoint.rb b/lib/chef_zero/endpoints/cookbook_artifact_identifier_endpoint.rb
new file mode 100644
index 0000000..b70e2c4
--- /dev/null
+++ b/lib/chef_zero/endpoints/cookbook_artifact_identifier_endpoint.rb
@@ -0,0 +1,68 @@
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ class CookbookArtifactIdentifierEndpoint < ChefZero::Endpoints::CookbookVersionEndpoint
+ # these endpoints are almost, but not quite, not entirely unlike the corresponding /cookbooks endpoints.
+ # it could all be refactored for maximum reuse, but they're short REST methods with well-defined
+ # behavioral specs (pedant), so there's not a huge benefit.
+
+ # GET /organizations/ORG/cookbook_artifacts/NAME/IDENTIFIER
+ def get(request)
+ cookbook_data = normalize(request, parse_json(get_data(request)))
+ return json_response(200, cookbook_data)
+ end
+
+ # PUT /organizations/ORG/cookbook_artifacts/COOKBOOK/IDENTIFIER
+ def put(request)
+ if exists_data?(request)
+ return error(409, "Cookbooks cannot be modified, and a cookbook with this identifier already exists.")
+ end
+
+ set_data(request, nil, request.body, :create_dir)
+
+ return already_json_response(201, request.body)
+ end
+
+ # DELETE /organizations/ORG/cookbook_artifacts/COOKBOOK/IDENTIFIER
+ def delete(request)
+ begin
+ doomed_cookbook_json = get_data(request)
+ identified_cookbook_data = normalize(request, parse_json(doomed_cookbook_json))
+ delete_data(request)
+
+ # go through the recipes and delete stuff in the file store.
+ hoover_unused_checksums(get_checksums(doomed_cookbook_json), request)
+
+ # if this was the last revision, delete the directory so future requests will 404, instead of
+ # returning 200 with an empty list.
+ # Last one out turns out the lights: delete /organizations/ORG/cookbooks/COOKBOOK if it no longer has versions
+ cookbook_path = request.rest_path[0..3]
+ if exists_data_dir?(request, cookbook_path) && list_data(request, cookbook_path).size == 0
+ delete_data_dir(request, cookbook_path)
+ end
+
+ json_response(200, identified_cookbook_data)
+ rescue RestErrorResponse => ex
+ if ex.response_code == 404
+ error(404, "not_found")
+ else
+ raise
+ end
+ end
+ end
+
+ private
+
+ def make_file_store_path(rest_path, recipe)
+ rest_path.first(2) + ["file_store", "checksums", recipe["checksum"]]
+ end
+
+ def normalize(request, cookbook_artifact_data)
+ ChefData::DataNormalizer.normalize_cookbook(self, request.rest_path[0..1],
+ cookbook_artifact_data, request.rest_path[3], request.rest_path[4],
+ request.base_uri, request.method, true)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/cookbook_artifacts_endpoint.rb b/lib/chef_zero/endpoints/cookbook_artifacts_endpoint.rb
new file mode 100644
index 0000000..d9fdb20
--- /dev/null
+++ b/lib/chef_zero/endpoints/cookbook_artifacts_endpoint.rb
@@ -0,0 +1,34 @@
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ class CookbookArtifactsEndpoint < RestBase
+ # GET /organizations/ORG/cookbook_artifacts
+ def get(request)
+ data = {}
+
+ artifacts = begin
+ list_data(request)
+ rescue Exception => e
+ if e.response_code == 404
+ return already_json_response(200, "{}")
+ end
+ end
+
+ artifacts.each do |cookbook_artifact|
+ cookbook_url = build_uri(request.base_uri, request.rest_path + [cookbook_artifact])
+
+ versions = []
+ list_data(request, request.rest_path + [cookbook_artifact]).each do |identifier|
+ artifact_url = build_uri(request.base_uri, request.rest_path + [cookbook_artifact, identifier])
+ versions << { url: artifact_url, identifier: identifier }
+ end
+
+ data[cookbook_artifact] = { url: cookbook_url, versions: versions }
+ end
+
+ return json_response(200, data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/cookbook_version_endpoint.rb b/lib/chef_zero/endpoints/cookbook_version_endpoint.rb
index 8dad508..5502ba0 100644
--- a/lib/chef_zero/endpoints/cookbook_version_endpoint.rb
+++ b/lib/chef_zero/endpoints/cookbook_version_endpoint.rb
@@ -58,8 +58,8 @@ module ChefZero
deleted_cookbook = get_data(request)
response = super(request)
- cookbook_name = request.rest_path[3]
- cookbook_path = request.rest_path[0..1] + ['cookbooks', cookbook_name]
+ # Last one out turns out the lights: delete /organizations/ORG/cookbooks/NAME if it no longer has versions
+ cookbook_path = request.rest_path[0..3]
if exists_data_dir?(request, cookbook_path) && list_data(request, cookbook_path).size == 0
delete_data_dir(request, cookbook_path)
end
@@ -86,10 +86,19 @@ module ChefZero
private
def hoover_unused_checksums(deleted_checksums, request)
- data_store.list(request.rest_path[0..1] + ['cookbooks']).each do |cookbook_name|
- data_store.list(request.rest_path[0..1] + ['cookbooks', cookbook_name]).each do |version|
- cookbook = data_store.get(request.rest_path[0..1] + ['cookbooks', cookbook_name, version], request)
- deleted_checksums = deleted_checksums - get_checksums(cookbook)
+ %w(cookbooks cookbook_artifacts).each do |cookbook_type|
+ begin
+ cookbooks = data_store.list(request.rest_path[0..1] + [cookbook_type])
+ rescue ChefZero::DataStore::DataNotFoundError
+ # Not all chef versions support cookbook_artifacts
+ raise unless cookbook_type == "cookbook_artifacts"
+ cookbooks = []
+ end
+ cookbooks.each do |cookbook_name|
+ data_store.list(request.rest_path[0..1] + [cookbook_type, cookbook_name]).each do |version|
+ cookbook = data_store.get(request.rest_path[0..1] + [cookbook_type, cookbook_name, version], request)
+ deleted_checksums = deleted_checksums - get_checksums(cookbook)
+ end
end
end
deleted_checksums.each do |checksum|
diff --git a/lib/chef_zero/endpoints/dummy_endpoint.rb b/lib/chef_zero/endpoints/dummy_endpoint.rb
new file mode 100644
index 0000000..fe16a7e
--- /dev/null
+++ b/lib/chef_zero/endpoints/dummy_endpoint.rb
@@ -0,0 +1,31 @@
+
+# pedant makes a couple of Solr-related calls from its search_utils.rb file that we can't work around (e.g.
+# with monkeypatching). the necessary Pedant::Config values are set in run_oc_pedant.rb. --cdoherty
+module ChefZero
+ module Endpoints
+ class DummyEndpoint < RestBase
+ # called by #direct_solr_query, once each for roles, nodes, and data bag items. each RSpec example makes
+ # 3 calls, with the expected sequence of return values [0, 1, 0].
+ def get(request)
+
+ # this could be made less brittle, but if things change to have more than 3 cycles, we should really
+ # be notified by a spec failure.
+ @mock_values ||= ([0, 1, 0] * 3).map { |val| make_response(val) }
+
+ retval = @mock_values.shift
+ json_response(200, retval)
+ end
+
+ # called by #force_solr_commit in pedant's , which doesn't check the return value.
+ def post(request)
+ # sure thing!
+ json_response(200, { message: "This dummy POST endpoint didn't do anything." })
+ end
+
+ def make_response(value)
+ { "response" => { "numFound" => value } }
+ end
+ end
+ end
+end
+
diff --git a/lib/chef_zero/endpoints/node_endpoint.rb b/lib/chef_zero/endpoints/node_endpoint.rb
index 223ec9f..f2bb8ba 100644
--- a/lib/chef_zero/endpoints/node_endpoint.rb
+++ b/lib/chef_zero/endpoints/node_endpoint.rb
@@ -6,6 +6,20 @@ module ChefZero
module Endpoints
# /nodes/ID
class NodeEndpoint < RestObjectEndpoint
+ def put(request)
+ data = parse_json(request.body)
+
+ if data.has_key?("policy_name") && policy_name_invalid?(data["policy_name"])
+ return error(400, "Field 'policy_name' invalid", :pretty => false)
+ end
+
+ if data.has_key?("policy_group") && policy_name_invalid?(data["policy_group"])
+ return error(400, "Field 'policy_group' invalid", :pretty => false)
+ end
+
+ super(request)
+ end
+
def populate_defaults(request, response_json)
node = FFI_Yajl::Parser.parse(response_json, :create_additions => false)
node = ChefData::DataNormalizer.normalize_node(node, request.rest_path[3])
diff --git a/lib/chef_zero/endpoints/nodes_endpoint.rb b/lib/chef_zero/endpoints/nodes_endpoint.rb
new file mode 100644
index 0000000..8b9d852
--- /dev/null
+++ b/lib/chef_zero/endpoints/nodes_endpoint.rb
@@ -0,0 +1,35 @@
+require 'ffi_yajl'
+require 'chef_zero/endpoints/rest_object_endpoint'
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /nodes
+ class NodesEndpoint < RestListEndpoint
+
+ def post(request)
+ # /nodes validation
+ if request.rest_path.last == "nodes"
+ data = parse_json(request.body)
+
+ if data.has_key?("policy_name") && policy_name_invalid?(data["policy_name"])
+ return error(400, "Field 'policy_name' invalid", :pretty => false)
+ end
+
+ if data.has_key?("policy_group") && policy_name_invalid?(data["policy_group"])
+ return error(400, "Field 'policy_group' invalid", :pretty => false)
+ end
+ end
+
+ super(request)
+ end
+
+ def populate_defaults(request, response_json)
+ node = FFI_Yajl::Parser.parse(response_json, :create_additions => false)
+ node = ChefData::DataNormalizer.normalize_node(node, request.rest_path[3])
+ FFI_Yajl::Encoder.encode(node, :pretty => true)
+ end
+ end
+ end
+end
+
diff --git a/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb b/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb
index e1b6a7e..72b0e4d 100644
--- a/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb
+++ b/lib/chef_zero/endpoints/organization_association_requests_endpoint.rb
@@ -6,7 +6,17 @@ module ChefZero
# /organizations/ORG/association_requests
class OrganizationAssociationRequestsEndpoint < RestBase
def post(request)
- ChefZero::Endpoints::OrganizationUserBase.post(self, request, 'user')
+ json = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
+ username = json['user']
+ orgname = request.rest_path[1]
+ id = "#{username}-#{orgname}"
+
+ if exists_data?(request, [ 'organizations', orgname, 'users', username ])
+ raise RestErrorResponse.new(409, "User #{username} is already in organization #{orgname}")
+ end
+
+ create_data(request, request.rest_path, username, '{}')
+ json_response(201, { "uri" => build_uri(request.base_uri, request.rest_path + [ id ]) })
end
def get(request)
diff --git a/lib/chef_zero/endpoints/organization_user_base.rb b/lib/chef_zero/endpoints/organization_user_base.rb
index aaa2e3a..d4ccf44 100644
--- a/lib/chef_zero/endpoints/organization_user_base.rb
+++ b/lib/chef_zero/endpoints/organization_user_base.rb
@@ -10,20 +10,6 @@ module ChefZero
obj.json_response(200, result)
end
- def self.post(obj, request, key)
- json = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
- username = json[key]
- orgname = request.rest_path[1]
- id = "#{username}-#{orgname}"
-
- if obj.exists_data?(request, [ 'organizations', orgname, 'users', username ])
- raise RestErrorResponse.new(409, "User #{username} is already in organization #{orgname}")
- end
-
- obj.create_data(request, request.rest_path, username, '{}')
- obj.json_response(201, { "uri" => obj.build_uri(request.base_uri, request.rest_path + [ id ]) })
- end
-
end
end
end
diff --git a/lib/chef_zero/endpoints/organization_user_default_key_endpoint.rb b/lib/chef_zero/endpoints/organization_user_default_key_endpoint.rb
new file mode 100644
index 0000000..953edc1
--- /dev/null
+++ b/lib/chef_zero/endpoints/organization_user_default_key_endpoint.rb
@@ -0,0 +1,16 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # GET /organizations/ORG/users/USER/keys/default
+ class OrganizationUserDefaultKeyEndpoint < RestBase
+ def get(request)
+ # 404 if it doesn't exist
+ get_data(request, request.rest_path[0..3])
+ # Just use the /users/USER/keys/default endpoint
+ request.rest_path = request.rest_path[2..-1]
+ ActorDefaultKeyEndpoint.new(server).get(request)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/organization_user_key_endpoint.rb b/lib/chef_zero/endpoints/organization_user_key_endpoint.rb
new file mode 100644
index 0000000..e0c114c
--- /dev/null
+++ b/lib/chef_zero/endpoints/organization_user_key_endpoint.rb
@@ -0,0 +1,17 @@
+require 'chef_zero/rest_base'
+require 'chef_zero/endpoints/actor_keys_endpoint'
+
+module ChefZero
+ module Endpoints
+ # GET /organizations/ORG/users/USER/keys/NAME
+ class OrganizationUserKeyEndpoint < RestBase
+ def get(request)
+ # 404 if not a member of the org
+ get_data(request, request.rest_path[0..3])
+ # Just use the /users/USER/keys endpoint
+ request.rest_path = request.rest_path[2..-1]
+ ActorKeyEndpoint.new(server).get(request)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/organization_user_keys_endpoint.rb b/lib/chef_zero/endpoints/organization_user_keys_endpoint.rb
new file mode 100644
index 0000000..96a84fe
--- /dev/null
+++ b/lib/chef_zero/endpoints/organization_user_keys_endpoint.rb
@@ -0,0 +1,17 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # GET /organizations/ORG/users/USER/keys
+ class OrganizationUserKeysEndpoint < RestBase
+ def get(request)
+ # 404 if it doesn't exist
+ get_data(request, request.rest_path[0..3])
+ # Just use the /users/USER/keys/key endpoint
+ original_path = request.rest_path
+ request.rest_path = request.rest_path[2..-1]
+ ActorKeysEndpoint.new(server).get(request, original_path)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/organization_users_endpoint.rb b/lib/chef_zero/endpoints/organization_users_endpoint.rb
index e914820..861c670 100644
--- a/lib/chef_zero/endpoints/organization_users_endpoint.rb
+++ b/lib/chef_zero/endpoints/organization_users_endpoint.rb
@@ -7,7 +7,32 @@ module ChefZero
# /organizations/ORG/users
class OrganizationUsersEndpoint < RestBase
def post(request)
- ChefZero::Endpoints::OrganizationUserBase.post(self, request, 'username')
+ orgname = request.rest_path[1]
+ json = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
+ username = json['username']
+
+ if exists_data?(request, [ 'organizations', orgname, 'users', username ])
+ raise RestErrorResponse.new(409, "User #{username} is already in organization #{orgname}")
+ end
+
+ users = get_data(request, [ 'organizations', orgname, 'groups', 'users' ])
+ users = FFI_Yajl::Parser.parse(users, :create_additions => false)
+
+ create_data(request, request.rest_path, username, '{}')
+
+ # /organizations/ORG/association_requests/USERNAME-ORG
+ begin
+ delete_data(request, [ 'organizations', orgname, 'association_requests', username], :data_store_exceptions)
+ rescue DataStore::DataNotFoundError
+ end
+
+ # Add the user to the users group if it isn't already there
+ if !users['users'] || !users['users'].include?(username)
+ users['users'] ||= []
+ users['users'] |= [ username ]
+ set_data(request, [ 'organizations', orgname, 'groups', 'users' ], FFI_Yajl::Encoder.encode(users, :pretty => true))
+ end
+ json_response(201, { "uri" => build_uri(request.base_uri, request.rest_path + [ username ]) })
end
def get(request)
diff --git a/lib/chef_zero/endpoints/policies_endpoint.rb b/lib/chef_zero/endpoints/policies_endpoint.rb
index 83d503c..37493da 100644
--- a/lib/chef_zero/endpoints/policies_endpoint.rb
+++ b/lib/chef_zero/endpoints/policies_endpoint.rb
@@ -1,154 +1,26 @@
-require 'ffi_yajl'
-
-require 'chef/version_class'
-require 'chef/exceptions'
-
-require 'chef_zero/endpoints/rest_object_endpoint'
require 'chef_zero/chef_data/data_normalizer'
module ChefZero
module Endpoints
- # /policies/:group/:name
- class PoliciesEndpoint < RestObjectEndpoint
- def initialize(server)
- super(server, 'id')
- end
-
+ # /organizations/ORG/policies
+ class PoliciesEndpoint < RestBase
+ # GET /organizations/ORG/policies
def get(request)
- already_json_response(200, get_data(request))
- end
-
- # Right now we're allowing PUT to create.
- def put(request)
- error = validate(request)
- return error if error
-
- code =
- if data_store.exists?(request.rest_path)
- set_data(request, request.rest_path, request.body, :data_store_exceptions)
- 200
- else
- name = request.rest_path[4]
- data_store.create(request.rest_path[0..3], name, request.body, :create_dir)
- 201
- end
- already_json_response(code, request.body)
- end
-
- def delete(request)
- result = get_data(request, request.rest_path)
- delete_data(request, request.rest_path, :data_store_exceptions)
- already_json_response(200, result)
- end
+ response_data = {}
+ policy_names = list_data(request)
+ policy_names.each do |policy_name|
+ policy_path = request.rest_path + [policy_name]
+ policy_uri = build_uri(request.base_uri, policy_path)
+ revisions = list_data(request, policy_path + ["revisions"])
- private
-
- def validate(request)
- req_object = validate_json(request.body)
- validate_revision_id(request, req_object) ||
- validate_name(request, req_object) ||
- validate_run_list(req_object) ||
- validate_each_run_list_item(req_object) ||
- validate_cookbook_locks_collection(req_object) ||
- validate_each_cookbook_locks_item(req_object)
- end
-
- def validate_json(request_body)
- FFI_Yajl::Parser.parse(request_body)
- # TODO: rescue parse error, return 400
- # error(400, "Must specify #{identity_keys.map { |k| k.inspect }.join(' or ')} in JSON")
- end
-
- def validate_revision_id(request, req_object)
- if !req_object.key?("revision_id")
- error(400, "Field 'revision_id' missing")
- elsif req_object["revision_id"].empty?
- error(400, "Field 'revision_id' invalid")
- elsif req_object["revision_id"].size > 255
- error(400, "Field 'revision_id' invalid")
- elsif req_object["revision_id"] !~ /^[\-[:alnum:]_\.\:]+$/
- error(400, "Field 'revision_id' invalid")
+ response_data[policy_name] = {
+ uri: policy_uri,
+ revisions: hashify_list(revisions)
+ }
end
- end
- def validate_name(request, req_object)
- if !req_object.key?("name")
- error(400, "Field 'name' missing")
- elsif req_object["name"] != (uri_policy_name = URI.decode(request.rest_path[4]))
- error(400, "Field 'name' invalid : #{uri_policy_name} does not match #{req_object["name"]}")
- elsif req_object["name"].size > 255
- error(400, "Field 'name' invalid")
- elsif req_object["name"] !~ /^[\-[:alnum:]_\.\:]+$/
- error(400, "Field 'name' invalid")
- end
- end
-
- def validate_run_list(req_object)
- if !req_object.key?("run_list")
- error(400, "Field 'run_list' missing")
- elsif !req_object["run_list"].kind_of?(Array)
- error(400, "Field 'run_list' is not a valid run list")
- end
+ return json_response(200, response_data)
end
-
- def validate_each_run_list_item(req_object)
- req_object["run_list"].each do |run_list_item|
- if res_400 = validate_run_list_item(run_list_item)
- return res_400
- end
- end
- nil
- end
-
- def validate_run_list_item(run_list_item)
- if !run_list_item.kind_of?(String)
- error(400, "Field 'run_list' is not a valid run list")
- elsif run_list_item !~ /\Arecipe\[[^\s]+::[^\s]+\]\Z/
- error(400, "Field 'run_list' is not a valid run list")
- end
- end
-
- def validate_cookbook_locks_collection(req_object)
- if !req_object.key?("cookbook_locks")
- error(400, "Field 'cookbook_locks' missing")
- elsif !req_object["cookbook_locks"].kind_of?(Hash)
- error(400, "Field 'cookbook_locks' invalid")
- end
- end
-
- def validate_each_cookbook_locks_item(req_object)
- req_object["cookbook_locks"].each do |cookbook_name, lock|
- if res_400 = validate_cookbook_locks_item(cookbook_name, lock)
- return res_400
- end
- end
- nil
- end
-
- def validate_cookbook_locks_item(cookbook_name, lock)
- if !lock.kind_of?(Hash)
- error(400, "cookbook_lock entries must be a JSON object")
- elsif !lock.key?("identifier")
- error(400, "Field 'identifier' missing")
- elsif lock["identifier"].size > 255
- error(400, "Field 'identifier' invalid")
- elsif !lock.key?("version")
- error(400, "Field 'version' missing")
- elsif lock.key?("dotted_decimal_identifier")
- unless valid_version?(lock["dotted_decimal_identifier"])
- error(400, "Field 'dotted_decimal_identifier' is not a valid version")
- end
- end
- end
-
- def valid_version?(version_string)
- Chef::Version.new(version_string)
- true
- rescue Chef::Exceptions::InvalidCookbookVersion
- false
- end
-
end
end
end
-
diff --git a/lib/chef_zero/endpoints/policy_endpoint.rb b/lib/chef_zero/endpoints/policy_endpoint.rb
new file mode 100644
index 0000000..d8c1bc8
--- /dev/null
+++ b/lib/chef_zero/endpoints/policy_endpoint.rb
@@ -0,0 +1,24 @@
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/policies/NAME
+ class PolicyEndpoint < RestBase
+ # GET /organizations/ORG/policies/NAME
+ def get(request)
+ revisions = list_data(request, request.rest_path + ["revisions"])
+ data = { revisions: hashify_list(revisions) }
+ return json_response(200, data)
+ end
+
+ # DELETE /organizations/ORG/policies/NAME
+ def delete(request)
+ revisions = list_data(request, request.rest_path + ["revisions"])
+ data = { revisions: hashify_list(revisions) }
+
+ delete_data_dir(request, nil, :recursive)
+ return json_response(200, data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/policy_group_endpoint.rb b/lib/chef_zero/endpoints/policy_group_endpoint.rb
new file mode 100644
index 0000000..54732c8
--- /dev/null
+++ b/lib/chef_zero/endpoints/policy_group_endpoint.rb
@@ -0,0 +1,46 @@
+require 'ffi_yajl'
+require 'chef_zero/rest_base'
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/policy_groups/NAME
+ class PolicyGroupEndpoint < RestBase
+
+ # GET /organizations/ORG/policy_groups/NAME
+ def get(request)
+ data = {
+ uri: build_uri(request.base_uri, request.rest_path),
+ policies: get_policy_group_policies(request)
+ }
+ json_response(200, data)
+ end
+
+ # build a hash of {"some_policy_name"=>{"revision_id"=>"909c26701e291510eacdc6c06d626b9fa5350d25"}}
+ def get_policy_group_policies(request)
+ policies_revisions = {}
+
+ policies_path = request.rest_path + ["policies"]
+ policy_names = list_data(request, policies_path)
+ policy_names.each do |policy_name|
+ revision = parse_json(get_data(request, policies_path + [policy_name]))
+ policies_revisions[policy_name] = { revision_id: revision}
+ end
+
+ policies_revisions
+ end
+
+ # DELETE /organizations/ORG/policy_groups/NAME
+ def delete(request)
+ policy_group_policies = get_policy_group_policies(request)
+ delete_data_dir(request, nil, :recursive)
+
+ data = {
+ uri: build_uri(request.base_uri, request.rest_path),
+ policies: policy_group_policies
+ }
+ json_response(200, data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb b/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb
new file mode 100644
index 0000000..d227905
--- /dev/null
+++ b/lib/chef_zero/endpoints/policy_group_policy_endpoint.rb
@@ -0,0 +1,84 @@
+require 'ffi_yajl'
+require 'chef_zero/rest_base'
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/policy_groups/GROUP/policies/NAME
+ #
+ # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently
+ # associated with ${policy_group}.
+ class PolicyGroupPolicyEndpoint < RestBase
+
+ # GET /organizations/ORG/policy_groups/GROUP/policies/NAME
+ def get(request)
+ policy_name = request.rest_path[5]
+
+ # fetch /organizations/{organization}/policies/{policy_name}/revisions/{revision_id}
+ revision_id = parse_json(get_data(request))
+ result = get_data(request, request.rest_path[0..1] +
+ ["policies", policy_name, "revisions", revision_id])
+ result = ChefData::DataNormalizer.normalize_policy(parse_json(result), policy_name, revision_id)
+ json_response(200, result)
+ end
+
+ # Create or update the policy document for the given policy group and policy name. If no policy group
+ # with the given name exists, it will be created. If no policy with the given revision_id exists, it
+ # will be created from the document in the request body. If a policy with that revision_id exists, the
+ # Chef Server simply associates that revision id with the given policy group. When successful, the
+ # document that was created or updated is returned.
+
+ ## MANDATORY FIELDS AND FORMATS
+ # * `revision_id`: String; Must be < 255 chars, matches /^[\-[:alnum:]_\.\:]+$/
+ # * `name`: String; Must match name in URI; Must be <= 255 chars, matches /^[\-[:alnum:]_\.\:]+$/
+ # * `run_list`: Array
+ # * `run_list[i]`: Fully Qualified Recipe Run List Item
+ # * `cookbook_locks`: JSON Object
+ # * `cookbook_locks(key)`: CookbookName
+ # * `cookbook_locks[item]`: JSON Object, mandatory keys: "identifier", "dotted_decimal_identifier"
+ # * `cookbook_locks[item]["identifier"]`: varchar(255) ?
+ # * `cookbook_locks[item]["dotted_decimal_identifier"]` ChefCompatibleVersionNumber
+
+ # PUT /organizations/ORG/policy_groups/GROUP/policies/NAME
+ def put(request)
+ policyfile_data = parse_json(request.body)
+ policy_name = request.rest_path[5]
+ revision_id = policyfile_data["revision_id"]
+
+ # If the policy revision being submitted does not exist, create it.
+ # Storage: /organizations/ORG/policies/POLICY/revisions/REVISION
+ policyfile_path = request.rest_path[0..1] + ["policies", policy_name, "revisions", revision_id]
+ if !exists_data?(request, policyfile_path)
+ create_data(request, policyfile_path[0..-2], revision_id, request.body, :create_dir)
+ end
+
+ # if named policy exists and the given revision ID exists, associate the revision ID with the policy
+ # group.
+ # Storage: /organizations/ORG/policies/POLICY/revisions/REVISION
+ response_code = exists_data?(request) ? 200 : 201
+ set_data(request, nil, to_json(revision_id), :create, :create_dir)
+
+ already_json_response(response_code, request.body)
+ end
+
+ # DELETE /organizations/ORG/policy_groups/GROUP/policies/NAME
+ def delete(request)
+ # Save the existing association.
+ current_revision_id = parse_json(get_data(request))
+
+ # delete the association.
+ delete_data(request)
+
+ # return the full policy document at the no-longer-associated revision.
+ policy_name = request.rest_path[5]
+ policy_path = request.rest_path[0..1] + ["policies", policy_name,
+ "revisions", current_revision_id]
+
+
+ full_policy_doc = parse_json(get_data(request, policy_path))
+ full_policy_doc = ChefData::DataNormalizer.normalize_policy(full_policy_doc, policy_name, current_revision_id)
+ return json_response(200, full_policy_doc)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/policy_groups_endpoint.rb b/lib/chef_zero/endpoints/policy_groups_endpoint.rb
new file mode 100644
index 0000000..f17db8d
--- /dev/null
+++ b/lib/chef_zero/endpoints/policy_groups_endpoint.rb
@@ -0,0 +1,38 @@
+require 'ffi_yajl'
+require 'chef_zero/rest_base'
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/policy_groups
+ #
+ # in the data store, this REST path actually stores the revision ID of ${policy_name} that's currently
+ # associated with ${policy_group}.
+ class PolicyGroupsEndpoint < RestBase
+ # GET /organizations/ORG/policy_groups
+ def get(request)
+ # each policy group has policies and associated revisions under
+ # /policy_groups/{group name}/policies/{policy name}.
+ response_data = {}
+ list_data(request).each do |group_name|
+ group_path = request.rest_path + [group_name]
+ policy_list = list_data(request, group_path + ["policies"])
+
+ # build the list of policies with their revision ID associated with this policy group.
+ policies = {}
+ policy_list.each do |policy_name|
+ revision_id = parse_json(get_data(request, group_path + ["policies", policy_name]))
+ policies[policy_name] = { revision_id: revision_id }
+ end
+
+ response_data[group_name] = {
+ uri: build_uri(request.base_uri, group_path)
+ }
+ response_data[group_name][:policies] = policies unless policies.empty?
+ end
+
+ json_response(200, response_data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/policy_revision_endpoint.rb b/lib/chef_zero/endpoints/policy_revision_endpoint.rb
new file mode 100644
index 0000000..6a77d26
--- /dev/null
+++ b/lib/chef_zero/endpoints/policy_revision_endpoint.rb
@@ -0,0 +1,23 @@
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/policies/NAME/revisions/REVISION
+ class PolicyRevisionEndpoint < RestBase
+ # GET /organizations/ORG/policies/NAME/revisions/REVISION
+ def get(request)
+ data = parse_json(get_data(request))
+ data = ChefData::DataNormalizer.normalize_policy(data, request.rest_path[3], request.rest_path[5])
+ return json_response(200, data)
+ end
+
+ # DELETE /organizations/ORG/policies/NAME/revisions/REVISION
+ def delete(request)
+ policyfile_data = parse_json(get_data(request))
+ policyfile_data = ChefData::DataNormalizer.normalize_policy(policyfile_data, request.rest_path[3], request.rest_path[5])
+ delete_data(request)
+ return json_response(200, policyfile_data)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/policy_revisions_endpoint.rb b/lib/chef_zero/endpoints/policy_revisions_endpoint.rb
new file mode 100644
index 0000000..7c20a24
--- /dev/null
+++ b/lib/chef_zero/endpoints/policy_revisions_endpoint.rb
@@ -0,0 +1,15 @@
+require 'chef_zero/chef_data/data_normalizer'
+
+module ChefZero
+ module Endpoints
+ # /organizations/ORG/policies/NAME/revisions
+ class PolicyRevisionsEndpoint < RestBase
+ # POST /organizations/ORG/policies/NAME/revisions
+ def post(request)
+ policyfile_data = parse_json(request.body)
+ create_data(request, request.rest_path, policyfile_data["revision_id"], request.body, :create_dir)
+ return already_json_response(201, request.body)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/endpoints/principal_endpoint.rb b/lib/chef_zero/endpoints/principal_endpoint.rb
index 8cad07c..64c6986 100644
--- a/lib/chef_zero/endpoints/principal_endpoint.rb
+++ b/lib/chef_zero/endpoints/principal_endpoint.rb
@@ -8,16 +8,20 @@ module ChefZero
class PrincipalEndpoint < RestBase
def get(request)
name = request.rest_path[-1]
+ # If /organizations/ORG/users/NAME exists, use this user (only org members have precedence over clients). hey are an org member.
json = get_data(request, request.rest_path[0..1] + [ 'users', name ], :nil)
if json
type = 'user'
org_member = true
else
+ # If /organizations/ORG/clients/NAME exists, use the client.
json = get_data(request, request.rest_path[0..1] + [ 'clients', name ], :nil)
if json
type = 'client'
org_member = true
else
+ # If there is no client with that name, check for a user (/users/NAME) and return that with
+ # org_member = false.
json = get_data(request, [ 'users', name ], :nil)
if json
type = 'user'
@@ -26,13 +30,22 @@ module ChefZero
end
end
if json
- json_response(200, {
+ principal_data = {
'name' => name,
'type' => type,
'public_key' => FFI_Yajl::Parser.parse(json)['public_key'] || PUBLIC_KEY,
'authz_id' => '0'*32,
'org_member' => org_member
- })
+ }
+
+ response_data =
+ if request.api_v0?
+ principal_data
+ else
+ { "principals" => [ principal_data ] }
+ end
+
+ json_response(200, response_data)
else
error(404, 'Principal not found')
end
diff --git a/lib/chef_zero/endpoints/rest_object_endpoint.rb b/lib/chef_zero/endpoints/rest_object_endpoint.rb
index 9e978b4..7e839c0 100644
--- a/lib/chef_zero/endpoints/rest_object_endpoint.rb
+++ b/lib/chef_zero/endpoints/rest_object_endpoint.rb
@@ -21,12 +21,11 @@ module ChefZero
def put(request)
# We grab the old body to trigger a 404 if it doesn't exist
old_body = get_data(request)
- request_json = FFI_Yajl::Parser.parse(request.body, :create_additions => false)
- key = identity_keys.map { |k| request_json[k] }.select { |v| v }.first
- key ||= request.rest_path[-1]
+
# If it's a rename, check for conflict and delete the old value
- rename = key != request.rest_path[-1]
- if rename
+ if is_rename?(request)
+ key = identity_key_value(request)
+
begin
create_data(request, request.rest_path[0..-2], key, request.body, :data_store_exceptions)
rescue DataStore::DataAlreadyExistsError
@@ -56,8 +55,23 @@ module ChefZero
return FFI_Yajl::Encoder.encode(merged_json, :pretty => true)
end
end
+
request.body
end
+
+ private
+
+ # Get the value of the (first existing) identity key from the request body or nil
+ def identity_key_value(request)
+ request_json = parse_json(request.body)
+ identity_keys.map { |k| request_json[k] }.compact.first
+ end
+
+ # Does this request change the value of the identity key?
+ def is_rename?(request)
+ return false unless key = identity_key_value(request)
+ key != request.rest_path[-1]
+ end
end
end
end
diff --git a/lib/chef_zero/endpoints/server_api_version_endpoint.rb b/lib/chef_zero/endpoints/server_api_version_endpoint.rb
new file mode 100644
index 0000000..8ddeaba
--- /dev/null
+++ b/lib/chef_zero/endpoints/server_api_version_endpoint.rb
@@ -0,0 +1,14 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # /server_api_version
+ class ServerAPIVersionEndpoint < RestBase
+ API_VERSION = 1
+ def get(request)
+ json_response(200, {"min_api_version"=>MIN_API_VERSION, "max_api_version"=>MAX_API_VERSION},
+ request_version: request.api_version, response_version: API_VERSION)
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/rest_base.rb b/lib/chef_zero/rest_base.rb
index 48d423a..71f6f15 100644
--- a/lib/chef_zero/rest_base.rb
+++ b/lib/chef_zero/rest_base.rb
@@ -5,6 +5,9 @@ require 'chef_zero/chef_data/acl_path'
module ChefZero
class RestBase
+ DEFAULT_REQUEST_VERSION = 0
+ DEFAULT_RESPONSE_VERSION = 0
+
def initialize(server)
@server = server
end
@@ -15,7 +18,36 @@ module ChefZero
server.data_store
end
+ def check_api_version(request)
+ return if request.api_version.nil? # Not present in headers
+ version = request.api_version.to_i
+
+ unless version.to_s == request.api_version.to_s # Version is not an Integer
+ return json_response(406,
+ { "username" => request.requestor },
+ request_version: -1, response_version: -1
+ )
+ end
+
+ if version > MAX_API_VERSION || version < MIN_API_VERSION
+ response = {
+ "error" => "invalid-x-ops-server-api-version",
+ "message" => "Specified version #{version} not supported",
+ "min_api_version" => MIN_API_VERSION,
+ "max_api_version" => MAX_API_VERSION
+ }
+
+ return json_response(406,
+ response,
+ request_version: version, response_version: -1
+ )
+ end
+ end
+
def call(request)
+ response = check_api_version(request)
+ return response unless response.nil?
+
method = request.method.downcase.to_sym
if !self.respond_to?(method)
accept_methods = [:get, :put, :post, :delete].select { |m| self.respond_to?(m) }
@@ -29,7 +61,7 @@ module ChefZero
begin
self.send(method, request)
rescue RestErrorResponse => e
- ChefZero::Log.debug("#{e.inspect}\n#{e.backtrace.join("\n")}")
+ ChefZero::Log.info("#{e.inspect}\n#{e.backtrace.join("\n")}")
error(e.response_code, e.error)
end
end
@@ -82,7 +114,7 @@ module ChefZero
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
end
end
@@ -101,7 +133,7 @@ module ChefZero
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
end
end
@@ -120,7 +152,7 @@ module ChefZero
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, request.rest_path)}")
+ raise RestErrorResponse.new(404, "Object not found: #{build_uri(request.base_uri, rest_path)}")
end
end
end
@@ -133,13 +165,13 @@ module ChefZero
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, request.rest_path)}")
+ raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}")
end
rescue DataStore::DataAlreadyExistsError
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, request.rest_path + [name])}")
+ raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}")
end
end
end
@@ -152,13 +184,13 @@ module ChefZero
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, request.rest_path)}")
+ raise RestErrorResponse.new(404, "Parent not found: #{build_uri(request.base_uri, rest_path)}")
end
rescue DataStore::DataAlreadyExistsError
if options.include?(:data_store_exceptions)
raise
else
- raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, request.rest_path + [name])}")
+ raise RestErrorResponse.new(409, "Object already exists: #{build_uri(request.base_uri, rest_path + [name])}")
end
end
end
@@ -173,20 +205,60 @@ module ChefZero
data_store.exists_dir?(rest_path)
end
- def error(response_code, error)
- json_response(response_code, {"error" => [error]})
+ def error(response_code, error, opts={})
+ json_response(response_code, { "error" => [ error ] }, opts)
end
- def json_response(response_code, json)
- already_json_response(response_code, FFI_Yajl::Encoder.encode(json, :pretty => true))
+ # Serializes `data` to JSON and returns an Array with the
+ # response code, HTTP headers and JSON body.
+ #
+ # @param [Fixnum] response_code HTTP response code
+ # @param [Hash] data The data for the response body as a Hash
+ # @param [Hash] options
+ # @option options [Hash] :headers (see #already_json_response)
+ # @option options [Boolean] :pretty (true) Pretty-format the JSON
+ # @option options [Fixnum] :request_version (see #already_json_response)
+ # @option options [Fixnum] :response_version (see #already_json_response)
+ #
+ # @return (see #already_json_response)
+ #
+ def json_response(response_code, data, options={})
+ options = { pretty: true }.merge(options)
+ do_pretty_json = !!options.delete(:pretty) # make sure we have a proper Boolean.
+ json = FFI_Yajl::Encoder.encode(data, pretty: do_pretty_json)
+ already_json_response(response_code, json, options)
end
def text_response(response_code, text)
[response_code, {"Content-Type" => "text/plain"}, text]
end
- def already_json_response(response_code, json_text)
- [response_code, {"Content-Type" => "application/json"}, json_text]
+ # Returns an Array with the response code, HTTP headers, and JSON body.
+ #
+ # @param [Fixnum] response_code The HTTP response code
+ # @param [String] json_text The JSON body for the response
+ # @param [Hash] options
+ # @option options [Hash] :headers ({}) HTTP headers (may override default headers)
+ # @option options [Fixnum] :request_version (0) Request API version
+ # @option options [Fixnum] :response_version (0) Response API version
+ #
+ # @return [Array(Fixnum, Hash{String => String}, String)]
+ #
+ def already_json_response(response_code, json_text, options={})
+ version_header = FFI_Yajl::Encoder.encode(
+ "min_version" => MIN_API_VERSION.to_s,
+ "max_version" => MAX_API_VERSION.to_s,
+ "request_version" => options[:request_version] || DEFAULT_REQUEST_VERSION.to_s,
+ "response_version" => options[:response_version] || DEFAULT_RESPONSE_VERSION.to_s
+ )
+
+ headers = {
+ "Content-Type" => "application/json",
+ "X-Ops-Server-API-Version" => version_header
+ }
+ headers.merge!(options[:headers]) if options[:headers]
+
+ [ response_code, headers, json_text ]
end
# To be called from inside rest endpoints
@@ -195,12 +267,12 @@ module ChefZero
# Strip off /organizations/chef if we are in single org mode
if rest_path[0..1] != [ 'organizations', server.options[:single_org] ]
raise "Unexpected URL #{rest_path[0..1]} passed to build_uri in single org mode"
- else
- "#{base_uri}/#{rest_path[2..-1].join('/')}"
end
- else
- "#{base_uri}/#{rest_path.join('/')}"
+
+ return self.class.build_uri(base_uri, rest_path[2..-1])
end
+
+ self.class.build_uri(base_uri, rest_path)
end
def self.build_uri(base_uri, rest_path)
@@ -210,5 +282,39 @@ module ChefZero
def populate_defaults(request, response)
response
end
+
+ def parse_json(json)
+ FFI_Yajl::Parser.parse(json, create_additions: false)
+ end
+
+ def to_json(data)
+ FFI_Yajl::Encoder.encode(data, :pretty => true)
+ end
+
+ def get_data_or_else(request, path, or_else_value)
+ if exists_data?(request, path)
+ parse_json(get_data(request, path))
+ else
+ or_else_value
+ end
+ end
+
+ def list_data_or_else(request, path, or_else_value)
+ if exists_data_dir?(request, path)
+ list_data(request, path)
+ else
+ or_else_value
+ end
+ end
+
+ def hashify_list(list)
+ list.reduce({}) { |acc, obj| acc.merge( obj => {} ) }
+ end
+
+ def policy_name_invalid?(name)
+ !name.is_a?(String) ||
+ name.size > 255 ||
+ name =~ /[+ !]/
+ end
end
end
diff --git a/lib/chef_zero/rest_error_response.rb b/lib/chef_zero/rest_error_response.rb
index e75d427..8859650 100644
--- a/lib/chef_zero/rest_error_response.rb
+++ b/lib/chef_zero/rest_error_response.rb
@@ -1,11 +1,11 @@
module ChefZero
class RestErrorResponse < StandardError
+ attr_reader :response_code, :error
+
def initialize(response_code, error)
@response_code = response_code
@error = error
+ super "#{response_code}: #{error}"
end
-
- attr_reader :response_code
- attr_reader :error
end
end
diff --git a/lib/chef_zero/rest_request.rb b/lib/chef_zero/rest_request.rb
index e79af7f..c12ea31 100644
--- a/lib/chef_zero/rest_request.rb
+++ b/lib/chef_zero/rest_request.rb
@@ -2,6 +2,9 @@ require 'rack/request'
module ChefZero
class RestRequest
+
+ ZERO = "0".freeze
+
def initialize(env, rest_base_prefix = [])
@env = env
@rest_base_prefix = rest_base_prefix
@@ -11,13 +14,27 @@ module ChefZero
attr_accessor :rest_base_prefix
def base_uri
- @base_uri ||= "#{env['rack.url_scheme']}://#{env['HTTP_HOST']}#{env['SCRIPT_NAME']}"
+ # Load balancer awareness
+ if env['HTTP_X_FORWARDED_PROTO']
+ scheme = env['HTTP_X_FORWARDED_PROTO']
+ else
+ scheme = env['rack.url_scheme']
+ end
+ @base_uri ||= "#{scheme}://#{env['HTTP_HOST']}#{env['SCRIPT_NAME']}"
end
def base_uri=(value)
@base_uri = value
end
+ def api_version
+ @env['HTTP_X_OPS_SERVER_API_VERSION'] || ZERO
+ end
+
+ def api_v0?
+ api_version == ZERO
+ end
+
def requestor
@env['HTTP_X_OPS_USERID']
end
@@ -30,6 +47,10 @@ module ChefZero
@rest_path ||= rest_base_prefix + env['PATH_INFO'].split('/').select { |part| part != "" }
end
+ def rest_path=(rest_path)
+ @rest_path = rest_path
+ end
+
def body=(body)
@body = body
end
diff --git a/lib/chef_zero/rest_router.rb b/lib/chef_zero/rest_router.rb
index f2770d3..a93af8b 100644
--- a/lib/chef_zero/rest_router.rb
+++ b/lib/chef_zero/rest_router.rb
@@ -1,3 +1,5 @@
+require 'pp'
+
module ChefZero
class RestRouter
def initialize(routes)
@@ -15,24 +17,18 @@ module ChefZero
attr_accessor :not_found
def call(request)
- begin
- ChefZero::Log.debug(request)
- ChefZero::Log.debug(request.body) if request.body
-
- clean_path = "/" + request.rest_path.join("/")
-
- response = find_endpoint(clean_path).call(request)
- ChefZero::Log.debug([
- "",
- "--- RESPONSE (#{response[0]}) ---",
- response[2],
- "--- END RESPONSE ---",
- ].join("\n"))
- return response
- rescue
- ChefZero::Log.error("#{$!.inspect}\n#{$!.backtrace.join("\n")}")
- [500, {"Content-Type" => "text/plain"}, "Exception raised! #{$!.inspect}\n#{$!.backtrace.join("\n")}"]
+ log_request(request)
+
+ clean_path = "/" + request.rest_path.join("/")
+
+ find_endpoint(clean_path).call(request).tap do |response|
+ log_response(response)
end
+ rescue => ex
+ exception = "#{ex.inspect}\n#{ex.backtrace.join("\n")}"
+
+ ChefZero::Log.error(exception)
+ [ 500, { "Content-Type" => "text/plain" }, "Exception raised! #{exception}" ]
end
private
@@ -41,5 +37,36 @@ module ChefZero
_, endpoint = routes.find { |route, endpoint| route.match(clean_path) }
endpoint || not_found
end
+
+ def log_request(request)
+ ChefZero::Log.debug do
+ "#{request.method} /#{request.rest_path.join("/")}".tap do |msg|
+ next unless request.method =~ /^(POST|PUT)$/
+
+ if request.body.nil? || request.body.empty?
+ msg << " (no body)"
+ else
+ msg << [
+ "",
+ "--- #{request.method} BODY ---",
+ request.body.chomp,
+ "--- END #{request.method} BODY ---"
+ ].join("\n")
+ end
+ end
+ end
+
+ ChefZero::Log.debug { request.pretty_inspect }
+ end
+
+ def log_response(response)
+ ChefZero::Log.debug {
+ [ "",
+ "--- RESPONSE (#{response[0]}) ---",
+ response[2].chomp,
+ "--- END RESPONSE ---",
+ ].join("\n")
+ }
+ end
end
end
diff --git a/lib/chef_zero/rspec.rb b/lib/chef_zero/rspec.rb
index 2dd9a3a..8867f37 100644
--- a/lib/chef_zero/rspec.rb
+++ b/lib/chef_zero/rspec.rb
@@ -67,7 +67,7 @@ module ChefZero
if chef_server_options[:server_scope] != self.class.chef_server_options[:server_scope]
raise "server_scope: #{chef_server_options[:server_scope]} will not be honored: it can only be set on when_the_chef_server!"
end
- Log.debug("Starting Chef server with options #{chef_server_options}")
+ Log.info("Starting Chef server with options #{chef_server_options}")
ChefZero::RSpec.set_server_options(chef_server_options)
@@ -125,6 +125,10 @@ module ChefZero
end
end
+ def cookbook_artifact(name, identifier, data = {}, &block)
+ before(chef_server_options[:server_scope]) { cookbook_artifact(name, identifier, data, &block) }
+ end
+
def data_bag(name, data, &block)
before(chef_server_options[:server_scope]) { data_bag(name, data, &block) }
end
@@ -149,6 +153,14 @@ module ChefZero
before(chef_server_options[:server_scope]) { org_member(*usernames) }
end
+ def policy(name, data, &block)
+ before(chef_server_options[:server_scope]) { policy(name, data, &block) }
+ end
+
+ def policy_group(name, data, &block)
+ before(chef_server_options[:server_scope]) { policy_group(name, data, &block) }
+ end
+
def role(name, data, &block)
before(chef_server_options[:server_scope]) { role(name, data, &block) }
end
@@ -203,6 +215,8 @@ module ChefZero
def cookbook(name, version, data = {}, options = {}, &block)
with_object_path("cookbooks/#{name}") do
+ # If you didn't specify metadata.rb, we generate it for you. If you
+ # explicitly set it to nil, that means you don't want it at all.
if data.has_key?('metadata.rb')
if data['metadata.rb'].nil?
data.delete('metadata.rb')
@@ -215,6 +229,22 @@ module ChefZero
end
end
+ def cookbook_artifact(name, identifier, data = {}, &block)
+ with_object_path("cookbook_artifacts/#{name}") do
+ # If you didn't specify metadata.rb, we generate it for you. If you
+ # explicitly set it to nil, that means you don't want it at all.
+ if data.has_key?('metadata.rb')
+ if data['metadata.rb'].nil?
+ data.delete('metadata.rb')
+ end
+ else
+ data['metadata.rb'] = "name #{name.inspect}"
+ end
+ ChefZero::RSpec.server.load_data({ 'cookbook_artifacts' => { "#{name}-#{identifier}" => data } }, current_org)
+ instance_eval(&block) if block_given?
+ end
+ end
+
def data_bag(name, data, &block)
with_object_path("data/#{name}") do
ChefZero::RSpec.server.load_data({ 'data' => { name => data }}, current_org)
@@ -251,6 +281,20 @@ module ChefZero
ChefZero::RSpec.server.load_data({ 'members' => usernames }, current_org)
end
+ def policy(name, version, data, &block)
+ with_object_path("policies/#{name}") do
+ ChefZero::RSpec.server.load_data({ 'policies' => { name => { version => data } } }, current_org)
+ instance_eval(&block) if block_given?
+ end
+ end
+
+ def policy_group(name, data, &block)
+ with_object_path("policy_groups/#{name}") do
+ ChefZero::RSpec.server.load_data({ 'policy_groups' => { name => data } }, current_org)
+ instance_eval(&block) if block_given?
+ end
+ end
+
def role(name, data, &block)
with_object_path("roles/#{name}") do
ChefZero::RSpec.server.load_data({ 'roles' => { name => data } }, current_org)
diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb
index 672f795..19744b1 100644
--- a/lib/chef_zero/server.rb
+++ b/lib/chef_zero/server.rb
@@ -40,13 +40,24 @@ require 'chef_zero/endpoints/rest_list_endpoint'
require 'chef_zero/endpoints/authenticate_user_endpoint'
require 'chef_zero/endpoints/acls_endpoint'
require 'chef_zero/endpoints/acl_endpoint'
-require 'chef_zero/endpoints/actors_endpoint'
require 'chef_zero/endpoints/actor_endpoint'
+require 'chef_zero/endpoints/actors_endpoint'
+require 'chef_zero/endpoints/actor_key_endpoint'
+require 'chef_zero/endpoints/organization_user_key_endpoint'
+require 'chef_zero/endpoints/organization_user_default_key_endpoint'
+require 'chef_zero/endpoints/organization_user_keys_endpoint'
+require 'chef_zero/endpoints/actor_default_key_endpoint'
+require 'chef_zero/endpoints/actor_keys_endpoint'
require 'chef_zero/endpoints/cookbooks_endpoint'
require 'chef_zero/endpoints/cookbook_endpoint'
require 'chef_zero/endpoints/cookbook_version_endpoint'
+require 'chef_zero/endpoints/cookbook_artifacts_endpoint'
+require 'chef_zero/endpoints/cookbook_artifact_endpoint'
+require 'chef_zero/endpoints/cookbook_artifact_identifier_endpoint'
require 'chef_zero/endpoints/containers_endpoint'
require 'chef_zero/endpoints/container_endpoint'
+require 'chef_zero/endpoints/controls_endpoint'
+require 'chef_zero/endpoints/dummy_endpoint'
require 'chef_zero/endpoints/data_bags_endpoint'
require 'chef_zero/endpoints/data_bag_endpoint'
require 'chef_zero/endpoints/data_bag_item_endpoint'
@@ -61,6 +72,7 @@ require 'chef_zero/endpoints/environment_recipes_endpoint'
require 'chef_zero/endpoints/environment_role_endpoint'
require 'chef_zero/endpoints/license_endpoint'
require 'chef_zero/endpoints/node_endpoint'
+require 'chef_zero/endpoints/nodes_endpoint'
require 'chef_zero/endpoints/node_identifiers_endpoint'
require 'chef_zero/endpoints/organizations_endpoint'
require 'chef_zero/endpoints/organization_endpoint'
@@ -70,8 +82,14 @@ require 'chef_zero/endpoints/organization_authenticate_user_endpoint'
require 'chef_zero/endpoints/organization_users_endpoint'
require 'chef_zero/endpoints/organization_user_endpoint'
require 'chef_zero/endpoints/organization_validator_key_endpoint'
-require 'chef_zero/endpoints/principal_endpoint'
require 'chef_zero/endpoints/policies_endpoint'
+require 'chef_zero/endpoints/policy_endpoint'
+require 'chef_zero/endpoints/policy_revisions_endpoint'
+require 'chef_zero/endpoints/policy_revision_endpoint'
+require 'chef_zero/endpoints/policy_groups_endpoint'
+require 'chef_zero/endpoints/policy_group_endpoint'
+require 'chef_zero/endpoints/policy_group_policy_endpoint'
+require 'chef_zero/endpoints/principal_endpoint'
require 'chef_zero/endpoints/role_endpoint'
require 'chef_zero/endpoints/role_environments_endpoint'
require 'chef_zero/endpoints/sandboxes_endpoint'
@@ -86,14 +104,16 @@ require 'chef_zero/endpoints/user_organizations_endpoint'
require 'chef_zero/endpoints/file_store_file_endpoint'
require 'chef_zero/endpoints/not_found_endpoint'
require 'chef_zero/endpoints/version_endpoint'
+require 'chef_zero/endpoints/server_api_version_endpoint'
module ChefZero
+
class Server
DEFAULT_OPTIONS = {
- :host => '127.0.0.1',
+ :host => ['127.0.0.1'],
:port => 8889,
- :log_level => :info,
+ :log_level => :warn,
:generate_real_keys => true,
:single_org => 'chef',
:ssl => false
@@ -102,6 +122,7 @@ module ChefZero
GLOBAL_ENDPOINTS = [
'/license',
'/version',
+ '/server_api_version'
]
def initialize(options = {})
@@ -121,6 +142,7 @@ module ChefZero
def port
if @port
@port
+ # If options[:port] is not an Array or an Enumerable, it is just an Integer.
elsif !options[:port].respond_to?(:each)
options[:port]
else
@@ -143,10 +165,11 @@ module ChefZero
#
def url
sch = @options[:ssl] ? 'https' : 'http'
- @url ||= if @options[:host].include?(':')
- URI("#{sch}://[#{@options[:host]}]:#{port}").to_s
+ hosts = Array(@options[:host])
+ @url ||= if hosts.first.include?(':')
+ URI("#{sch}://[#{hosts.first}]:#{port}").to_s
else
- URI("#{sch}://#{@options[:host]}:#{port}").to_s
+ URI("#{sch}://#{hosts.first}:#{port}").to_s
end
end
@@ -244,12 +267,26 @@ module ChefZero
# @return [Thread]
# the thread the background process is running in
#
+ def listen(hosts, port)
+ hosts.each do |host|
+ @server.listen(host, port)
+ end
+ true
+ rescue Errno::EADDRINUSE
+ ChefZero::Log.warn("Port #{port} not available")
+ @server.listeners.each { |l| l.close }
+ @server.listeners.clear
+ false
+ end
+
def start_background(wait = 5)
@server = WEBrick::HTTPServer.new(
:DoNotListen => true,
:AccessLog => [],
:Logger => WEBrick::Log.new(StringIO.new, 7),
+ :RequestTimeout => 300,
:SSLEnable => options[:ssl],
+ :SSLOptions => ssl_opts,
:SSLCertName => [ [ 'CN', WEBrick::Utils::getservername ] ],
:StartCallback => proc {
@running = true
@@ -259,22 +296,17 @@ module ChefZero
@server.mount('/', Rack::Handler::WEBrick, app)
# Pick a port
- if options[:port].respond_to?(:each)
- options[:port].each do |port|
- begin
- @server.listen(options[:host], port)
- @port = port
- break
- rescue Errno::EADDRINUSE
- ChefZero::Log.info("Port #{port} in use: #{$!}")
- end
+ # If options[:port] can be an Enumerator, an Array, or an Integer,
+ # we need something that can respond to .each (Enum and Array can already).
+ Array(options[:port]).each do |port|
+ if listen(Array(options[:host]), port)
+ @port = port
+ break
end
- if !@port
- raise Errno::EADDRINUSE, "No port in :port range #{options[:port]} is available"
- end
- else
- @server.listen(options[:host], options[:port])
- @port = options[:port]
+ end
+ if !@port
+ raise Errno::EADDRINUSE,
+ "No port in :port range #{options[:port]} is available"
end
# Start the server in the background
@@ -441,20 +473,43 @@ module ChefZero
end
end
- if contents['cookbooks']
- contents['cookbooks'].each_pair do |name_version, cookbook|
- if name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
- cookbook_data = ChefData::CookbookData.to_hash(cookbook, $1, $2)
- else
- cookbook_data = ChefData::CookbookData.to_hash(cookbook, name_version)
+ if contents['policies']
+ contents['policies'].each_pair do |policy_name, policy_struct|
+ # data_store.create_dir(['organizations', org_name, 'policies', policy_name], "revisions", :recursive)
+ dejsonize_children(policy_struct).each do |revision, policy_data|
+ data_store.set(['organizations', org_name, 'policies', policy_name,
+ "revisions", revision], policy_data, :create, :create_dir)
+ end
+ end
+ end
+
+ if contents['policy_groups']
+ contents['policy_groups'].each_pair do |group_name, group|
+ group['policies'].each do |policy_name, policy_revision|
+ data_store.set(['organizations', org_name, 'policy_groups', group_name, 'policies', policy_name], FFI_Yajl::Encoder.encode(policy_revision['revision_id'], :pretty => true), :create, :create_dir)
end
- raise "No version specified" if !cookbook_data[:version]
- data_store.create_dir(['organizations', org_name, 'cookbooks'], cookbook_data[:cookbook_name], :recursive)
- data_store.set(['organizations', org_name, 'cookbooks', cookbook_data[:cookbook_name], cookbook_data[:version]], FFI_Yajl::Encoder.encode(cookbook_data, :pretty => true), :create)
- cookbook_data.values.each do |files|
- next unless files.is_a? Array
- files.each do |file|
- data_store.set(['organizations', org_name, 'file_store', 'checksums', file[:checksum]], get_file(cookbook, file[:path]), :create)
+ end
+ end
+
+ %w(cookbooks cookbook_artifacts).each do |cookbook_type|
+ if contents[cookbook_type]
+ contents[cookbook_type].each_pair do |name_version, cookbook|
+ if cookbook_type == 'cookbook_artifacts'
+ name, dash, identifier = name_version.rpartition('-')
+ cookbook_data = ChefData::CookbookData.to_hash(cookbook, name, identifier)
+ elsif name_version =~ /(.+)-(\d+\.\d+\.\d+)$/
+ cookbook_data = ChefData::CookbookData.to_hash(cookbook, $1, $2)
+ else
+ cookbook_data = ChefData::CookbookData.to_hash(cookbook, name_version)
+ end
+ raise "No version specified" if !cookbook_data[:version]
+ data_store.create_dir(['organizations', org_name, cookbook_type], cookbook_data[:cookbook_name], :recursive)
+ data_store.set(['organizations', org_name, cookbook_type, cookbook_data[:cookbook_name], cookbook_data[:version]], FFI_Yajl::Encoder.encode(cookbook_data, :pretty => true), :create)
+ cookbook_data.values.each do |files|
+ next unless files.is_a? Array
+ files.each do |file|
+ data_store.set(['organizations', org_name, 'file_store', 'checksums', file[:checksum]], get_file(cookbook, file[:path]), :create)
+ end
end
end
end
@@ -479,13 +534,13 @@ module ChefZero
private
- def open_source_endpoints
+ def endpoints
result = if options[:osc_compat]
# OSC-only
[
[ "/organizations/*/users", ActorsEndpoint.new(self) ],
[ "/organizations/*/users/*", ActorEndpoint.new(self) ],
- [ "/organizations/*/authenticate_user", OrganizationAuthenticateUserEndpoint.new(self) ],
+ [ "/organizations/*/authenticate_user", OrganizationAuthenticateUserEndpoint.new(self) ]
]
else
# EC-only
@@ -499,11 +554,13 @@ module ChefZero
[ "/users/*/association_requests", UserAssociationRequestsEndpoint.new(self) ],
[ "/users/*/association_requests/count", UserAssociationRequestsCountEndpoint.new(self) ],
[ "/users/*/association_requests/*", UserAssociationRequestEndpoint.new(self) ],
+ [ "/users/*/keys", ActorKeysEndpoint.new(self) ],
+ [ "/users/*/keys/default", ActorDefaultKeyEndpoint.new(self) ],
+ [ "/users/*/keys/*", ActorKeyEndpoint.new(self) ],
[ "/users/*/organizations", UserOrganizationsEndpoint.new(self) ],
[ "/authenticate_user", AuthenticateUserEndpoint.new(self) ],
[ "/system_recovery", SystemRecoveryEndpoint.new(self) ],
[ "/license", LicenseEndpoint.new(self) ],
-
[ "/organizations", OrganizationsEndpoint.new(self) ],
[ "/organizations/*", OrganizationEndpoint.new(self) ],
[ "/organizations/*/_validator_key", OrganizationValidatorKeyEndpoint.new(self) ],
@@ -523,11 +580,22 @@ module ChefZero
end
result + [
# Both
+ [ "/dummy", DummyEndpoint.new(self) ],
[ "/organizations/*/clients", ActorsEndpoint.new(self) ],
[ "/organizations/*/clients/*", ActorEndpoint.new(self) ],
+ [ "/organizations/*/clients/*/keys", ActorKeysEndpoint.new(self) ],
+ [ "/organizations/*/clients/*/keys/default", ActorDefaultKeyEndpoint.new(self) ],
+ [ "/organizations/*/clients/*/keys/*", ActorKeyEndpoint.new(self) ],
+ [ "/organizations/*/users/*/keys", OrganizationUserKeysEndpoint.new(self) ],
+ [ "/organizations/*/users/*/keys/default", OrganizationUserDefaultKeyEndpoint.new(self) ],
+ [ "/organizations/*/users/*/keys/*", OrganizationUserKeyEndpoint.new(self) ],
+ [ "/organizations/*/controls", ControlsEndpoint.new(self) ],
[ "/organizations/*/cookbooks", CookbooksEndpoint.new(self) ],
[ "/organizations/*/cookbooks/*", CookbookEndpoint.new(self) ],
[ "/organizations/*/cookbooks/*/*", CookbookVersionEndpoint.new(self) ],
+ [ "/organizations/*/cookbook_artifacts", CookbookArtifactsEndpoint.new(self) ],
+ [ "/organizations/*/cookbook_artifacts/*", CookbookArtifactEndpoint.new(self) ],
+ [ "/organizations/*/cookbook_artifacts/*/*", CookbookArtifactIdentifierEndpoint.new(self) ],
[ "/organizations/*/data", DataBagsEndpoint.new(self) ],
[ "/organizations/*/data/*", DataBagEndpoint.new(self) ],
[ "/organizations/*/data/*/*", DataBagItemEndpoint.new(self) ],
@@ -539,10 +607,16 @@ module ChefZero
[ "/organizations/*/environments/*/nodes", EnvironmentNodesEndpoint.new(self) ],
[ "/organizations/*/environments/*/recipes", EnvironmentRecipesEndpoint.new(self) ],
[ "/organizations/*/environments/*/roles/*", EnvironmentRoleEndpoint.new(self) ],
- [ "/organizations/*/nodes", RestListEndpoint.new(self) ],
+ [ "/organizations/*/nodes", NodesEndpoint.new(self) ],
[ "/organizations/*/nodes/*", NodeEndpoint.new(self) ],
[ "/organizations/*/nodes/*/_identifiers", NodeIdentifiersEndpoint.new(self) ],
- [ "/organizations/*/policies/*/*", PoliciesEndpoint.new(self) ],
+ [ "/organizations/*/policies", PoliciesEndpoint.new(self) ],
+ [ "/organizations/*/policies/*", PolicyEndpoint.new(self) ],
+ [ "/organizations/*/policies/*/revisions", PolicyRevisionsEndpoint.new(self) ],
+ [ "/organizations/*/policies/*/revisions/*", PolicyRevisionEndpoint.new(self) ],
+ [ "/organizations/*/policy_groups", PolicyGroupsEndpoint.new(self) ],
+ [ "/organizations/*/policy_groups/*", PolicyGroupEndpoint.new(self) ],
+ [ "/organizations/*/policy_groups/*/policies/*", PolicyGroupPolicyEndpoint.new(self) ],
[ "/organizations/*/principals/*", PrincipalEndpoint.new(self) ],
[ "/organizations/*/roles", RestListEndpoint.new(self) ],
[ "/organizations/*/roles/*", RoleEndpoint.new(self) ],
@@ -553,6 +627,7 @@ module ChefZero
[ "/organizations/*/search", SearchesEndpoint.new(self) ],
[ "/organizations/*/search/*", SearchEndpoint.new(self) ],
[ "/version", VersionEndpoint.new(self) ],
+ [ "/server_api_version", ServerAPIVersionEndpoint.new(self) ],
# Internal
[ "/organizations/*/file_store/**", FileStoreFileEndpoint.new(self) ]
@@ -567,7 +642,7 @@ module ChefZero
def app
return @app if @app
- router = RestRouter.new(open_source_endpoints)
+ router = RestRouter.new(endpoints)
router.not_found = NotFoundEndpoint.new
if options[:single_org]
@@ -635,5 +710,16 @@ module ChefZero
end
value
end
+
+ ## Disable unsecure ssl
+ ## Ref: https://www.ruby-lang.org/en/news/2014/10/27/changing-default-settings-of-ext-openssl/
+ def ssl_opts
+ ssl_opts = OpenSSL::SSL::OP_ALL
+ ssl_opts &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
+ ssl_opts |= OpenSSL::SSL::OP_NO_COMPRESSION if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
+ ssl_opts |= OpenSSL::SSL::OP_NO_SSLv2 if defined?(OpenSSL::SSL::OP_NO_SSLv2)
+ ssl_opts |= OpenSSL::SSL::OP_NO_SSLv3 if defined?(OpenSSL::SSL::OP_NO_SSLv3)
+ ssl_opts
+ end
end
end
diff --git a/lib/chef_zero/version.rb b/lib/chef_zero/version.rb
index 274c30f..d39f82c 100644
--- a/lib/chef_zero/version.rb
+++ b/lib/chef_zero/version.rb
@@ -1,3 +1,3 @@
module ChefZero
- VERSION = '4.2.3'
+ VERSION = '4.7.1'
end
diff --git a/spec/run_oc_pedant.rb b/spec/run_oc_pedant.rb
index c0ba702..d874fb8 100644
--- a/spec/run_oc_pedant.rb
+++ b/spec/run_oc_pedant.rb
@@ -5,56 +5,193 @@ require 'bundler/setup'
require 'chef_zero/server'
require 'rspec/core'
-tmpdir = nil
+# This file runs oc-chef-pedant specs and is invoked by `rake pedant`
+# and other Rake tasks. Run `rake -T` to list tasks.
+#
+# Options for oc-chef-pedant and rspec can be specified via
+# ENV['PEDANT_OPTS'] and ENV['RSPEC_OPTS'], respectively.
+#
+# The log level can be specified via ENV['LOG_LEVEL'].
+#
+# Example:
+#
+# $ PEDANT_OPTS="--focus-users --skip-keys" \
+# > RSPEC_OPTS="--fail-fast --profile 5" \
+# > LOG_LEVEL=debug \
+# > rake pedant
+#
-begin
- if ENV['FILE_STORE']
- require 'tmpdir'
- require 'chef_zero/data_store/raw_file_store'
- tmpdir = Dir.mktmpdir
- data_store = ChefZero::DataStore::RawFileStore.new(tmpdir, true)
- data_store = ChefZero::DataStore::DefaultFacade.new(data_store, false, false)
- server = ChefZero::Server.new(:port => 8889, :single_org => false, :data_store => data_store)
- server.start_background
+DEFAULT_SERVER_OPTIONS = {
+ port: 8889,
+ single_org: false,
+}.freeze
- else
- server = ChefZero::Server.new(:port => 8889, :single_org => false)#, :log_level => :debug)
- server.start_background
+DEFAULT_LOG_LEVEL = :warn
+
+def log_level
+ return ENV['LOG_LEVEL'].downcase.to_sym if ENV['LOG_LEVEL']
+ return :debug if ENV['DEBUG']
+ DEFAULT_LOG_LEVEL
+end
+
+def start_chef_server(opts={})
+ opts = DEFAULT_SERVER_OPTIONS.merge(opts)
+ opts[:log_level] = log_level
+
+ ChefZero::Server.new(opts).tap {|server| server.start_background }
+end
+
+def start_cheffs_server(chef_repo_path)
+ require 'chef/version'
+ require 'chef/config'
+ require 'chef/chef_fs/config'
+ require 'chef/chef_fs/chef_fs_data_store'
+ require 'chef_zero/server'
+
+ Dir.mkdir(chef_repo_path) if !File.exists?(chef_repo_path)
+
+ # 11.6 and below had a bug where it couldn't create the repo children automatically
+ if Chef::VERSION.to_f < 11.8
+ %w(clients cookbooks data_bags environments nodes roles users).each do |child|
+ Dir.mkdir("#{chef_repo_path}/#{child}") if !File.exists?("#{chef_repo_path}/#{child}")
+ end
end
+ # Start the new server
+ Chef::Config.repo_mode = 'hosted_everything'
+ Chef::Config.chef_repo_path = chef_repo_path
+ Chef::Config.versioned_cookbooks = true
+ chef_fs_config = Chef::ChefFS::Config.new
+
+ data_store = Chef::ChefFS::ChefFSDataStore.new(chef_fs_config.local_fs, chef_fs_config.chef_config)
+ data_store = ChefZero::DataStore::V1ToV2Adapter.new(data_store, 'pedant-testorg')
+ data_store = ChefZero::DataStore::DefaultFacade.new(data_store, 'pedant-testorg', false)
+ data_store.create(%w(organizations pedant-testorg users), 'pivotal', '{}')
+ data_store.set(%w(organizations pedant-testorg groups admins), '{ "users": [ "pivotal" ] }')
+ data_store.set(%w(organizations pedant-testorg groups users), '{ "users": [ "pivotal" ] }')
+
+ start_chef_server(data_store: data_store)
+end
+
+def pedant_args_from_env
+ args_from_env('PEDANT_OPTS')
+end
+
+def rspec_args_from_env
+ args_from_env('RSPEC_OPTS')
+end
+
+def args_from_env(key)
+ return [] unless ENV[key]
+ ENV[key].split
+end
+
+begin
+ tmpdir = nil
+ server =
+ if ENV['FILE_STORE']
+ require 'tmpdir'
+ require 'chef_zero/data_store/raw_file_store'
+ tmpdir = Dir.mktmpdir
+ data_store = ChefZero::DataStore::RawFileStore.new(tmpdir, true)
+ data_store = ChefZero::DataStore::DefaultFacade.new(data_store, false, false)
+
+ start_chef_server(data_store: data_store)
+
+ elsif ENV['CHEF_FS']
+ require 'tmpdir'
+ tmpdir = Dir.mktmpdir
+ start_cheffs_server(tmpdir)
+
+ else
+ start_chef_server
+ end
+
require 'rspec/core'
require 'pedant'
require 'pedant/organization'
- #Pedant::Config.rerun = true
+ # Pedant::Config.rerun = true
Pedant.config.suite = 'api'
- Pedant.config.internal_server = 'http://localhost:8889'
+
Pedant.config[:config_file] = 'spec/support/oc_pedant.rb'
- Pedant.config[:server_api_version] = 0
- Pedant.setup([
- '--skip-knife',
- '--skip-keys',
- '--skip-controls',
- '--skip-acl',
+
+ # Because ChefFS can only ever have one user (pivotal), we can't do most of the
+ # tests that involve multiple
+ chef_fs_skips = if ENV['CHEF_FS']
+ [ '--skip-association',
+ '--skip-users',
+ '--skip-organizations',
+ '--skip-multiuser',
+ '--skip-user-keys',
+
+ # chef-zero has some non-removable quirks, such as the fact that files
+ # with 255-character names cannot be stored in local mode. This is
+ # reserved only for quirks that are *irrevocable* and by design; and
+ # should barely be used at all.
+ '--skip-chef-zero-quirks',
+ ]
+ else
+ []
+ end
+ # The latest released Chef doesn't do ACLs, Cookbook Artifacts or Policies yet
+ chef_fs_skips << '--skip-acl'
+ chef_fs_skips << '--skip-cookbook-artifacts'
+ chef_fs_skips << '--skip-policies'
+
+ # Multi-keys don't work prior to 12.8
+ unless Gem::Requirement.new(">= 12.8.0").satisfied_by?(Gem::Version.new(Chef::VERSION))
+ chef_fs_skips << '--skip-keys'
+ end
+
+ # These things aren't supported by Chef Zero in any mode of operation:
+ default_skips = [
+ # "the goal is that only authorization, authentication and validation tests
+ # are turned off" - @jkeiser
+ #
+ # ...but we're not there yet
+
+ # Chef Zero does not intend to support validation the way erchef does.
'--skip-validation',
+
+ # Chef Zero does not intend to support authentication the way erchef does.
'--skip-authentication',
+
+ # Chef Zero does not intend to support authorization the way erchef does.
'--skip-authorization',
+
+ # Omnibus tests depend on erchef features that are specific to erchef and
+ # bundled in the omnibus package. Currently the only test in this category
+ # is for the search reindexing script.
'--skip-omnibus',
+
+ # USAGs (user-specific association groups) are Authz groups that contain
+ # only one user and represent that user's association with an org. Though
+ # there are good reasons for them, they don't work well in practice and
+ # only the manage console really uses them. Since Chef Zero + Manage is a
+ # quite unusual configuration, we're ignoring them.
'--skip-usags',
- '--exclude-internal-orgs',
- '--skip-headers',
# Chef 12 features not yet 100% supported by Chef Zero
- '--skip-policies',
- '--skip-server-api-version',
- '--skip-cookbook-artifacts',
- '--skip-containers',
- '--skip-api-v1'
- ])
+ # The universe endpoint is unlikely to ever make sense for Chef Zero
+ '--skip-universe',
+ ]
+
+ # The knife tests are very slow and don't give us a lot of extra coverage,
+ # so we run them in a different entry in the travis test matrix.
+ pedant_args =
+ if ENV["PEDANT_KNIFE_TESTS"]
+ default_skips + %w{ --focus-knife }
+ else
+ default_skips + chef_fs_skips + %w{ --skip-knife }
+ end
+
+ Pedant.setup(pedant_args + pedant_args_from_env)
- result = RSpec::Core::Runner.run(Pedant.config.rspec_args)
+ rspec_args = Pedant.config.rspec_args + rspec_args_from_env
+ result = RSpec::Core::Runner.run(rspec_args)
server.stop if server.running?
ensure
diff --git a/spec/server_spec.rb b/spec/server_spec.rb
index 558ef35..123a13e 100644
--- a/spec/server_spec.rb
+++ b/spec/server_spec.rb
@@ -1,4 +1,5 @@
require 'chef_zero/server'
+require 'net/http'
require 'uri'
describe ChefZero::Server do
@@ -26,6 +27,10 @@ describe ChefZero::Server do
expect { ChefZero::Server.new(:port => 8889.upto(8889)).start_background }.to raise_error Errno::EADDRINUSE
end
+ it 'has a very patient request timeout' do
+ expect(@server.server.config[:RequestTimeout]).to eq 300
+ end
+
context 'accept headers' do
def get_nodes(accepts)
uri = URI(@server.url)
diff --git a/spec/support/oc_pedant.rb b/spec/support/oc_pedant.rb
index e3bcf27..6e39878 100644
--- a/spec/support/oc_pedant.rb
+++ b/spec/support/oc_pedant.rb
@@ -45,6 +45,16 @@ maximum_search_time 0
# # to be enabled for Pedant tests to work correctly
explicit_port_url true
+server_api_version 0
+
+internal_server chef_server
+
+# see dummy_endpoint.rb for details.
+search_server chef_server
+search_commit_url "/dummy"
+search_url_fmt "/dummy?fq=+X_CHEF_type_CHEF_X:%{type}&q=%{query}&wt=json"
+
+
# We're starting to break tests up into groups based on different
# criteria. The proper API tests (the results of which are viewable
# to OPC customers) should be the only ones run by Pedant embedded in
@@ -59,9 +69,12 @@ explicit_port_url true
# value.
include_internal false
-# This is the bit that is different from pedant.rb
-org({:name => "pedant-testorg",
- :create_me => true})
+key = 'spec/support/stickywicket.pem'
+
+org(name: "pedant-testorg",
+ create_me: !ENV['CHEF_FS'],
+ validator_key: key)
+
internal_account_url chef_server
delete_org true
@@ -72,11 +85,16 @@ delete_org true
# are using pre-existing users, you must supply a ':key_file' key,
# which should be the fully-qualified path /on the machine Pedant is
# running on/ to a private key for that user.
-key = 'spec/support/stickywicket.pem'
superuser_name 'pivotal'
superuser_key key
webui_key key
+def cheffs_or_else_user(value)
+ ENV['CHEF_FS'] ? "pivotal" : value
+end
+
+keyfile_maybe = ENV['CHEF_FS'] ? { key_file: key } : { key_file: nil }
+
requestors({
:clients => {
# The the admin user, for the purposes of getting things rolling
@@ -102,24 +120,26 @@ requestors({
:users => {
# An administrator in the testing organization
:admin => {
- :name => "pedant_admin_user",
- :create_me => true,
+ :name => cheffs_or_else_user("pedant_admin_user"),
+ :create_me => !ENV['CHEF_FS'],
+ :associate => !ENV['CHEF_FS'],
:create_knife => true
- },
+ }.merge(keyfile_maybe),
:non_admin => {
- :name => "pedant_user",
- :create_me => true,
+ :name => cheffs_or_else_user("pedant_user"),
+ :create_me => !ENV['CHEF_FS'],
+ :associate => !ENV['CHEF_FS'],
:create_knife => true
- },
+ }.merge(keyfile_maybe),
# A user that is not a member of the testing organization
:bad => {
- :name => "pedant-nobody",
- :create_me => true,
+ :name => cheffs_or_else_user("pedant-nobody"),
+ :create_me => !ENV['CHEF_FS'],
:create_knife => true,
:associate => false
- },
+ }.merge(keyfile_maybe),
}
})