summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md149
-rw-r--r--chef-zero.gemspec2
-rw-r--r--lib/chef_zero/endpoints/version_endpoint.rb12
-rw-r--r--lib/chef_zero/rest_base.rb7
-rw-r--r--lib/chef_zero/server.rb45
-rw-r--r--lib/chef_zero/socketless_server_map.rb84
-rw-r--r--lib/chef_zero/version.rb2
-rw-r--r--spec/server_spec.rb9
-rw-r--r--spec/socketless_server_map_spec.rb71
9 files changed, 302 insertions, 79 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 26dfc27..322c820 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
Chef Zero CHANGELOG
===================
+# unreleased
+
+* [PR#121](https://github.com/chef/chef-zero/pull/121): Add Socketless
+ mode.
+* [**Phil Dibowitz**](https://github.com/jaymzh):
+ Added support for /version
+
# 4.0 (2/11/2014)
- Add policyfile endpoints
@@ -8,218 +15,218 @@ Chef Zero CHANGELOG
# 3.2 (9/26/2014)
-- removed 'json' gem dependency, replaced it with 'ffi-yajl'
+* removed 'json' gem dependency, replaced it with 'ffi-yajl'
# 3.1.3 (9/3/2014)
-- fixes for running Chef local mode in multi-org mode
+* fixes for running Chef local mode in multi-org mode
# 3.1.2 (8/29/2014)
-- add default to rspec for cookbooks
-- add /organizations/NAME/organization/_acl as an alias for /organizations/NAME/organizations/_acl
+* add default to rspec for cookbooks
+* add /organizations/NAME/organization/_acl as an alias for /organizations/NAME/organizations/_acl
# 3.1.1 (8/28/2014)
-- fix minor bug with unknown container acls
+* fix minor bug with unknown container acls
# 3.1 (8/28/2014)
-- 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
+* 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
# 3.0 (7/22/2014)
-- Enterprise Chef support (organizations, ACLs, groups, much more)
-- SSL support (@sawanoboly)
+* Enterprise Chef support (organizations, ACLs, groups, much more)
+* SSL support (@sawanoboly)
# 2.2 (6/18/2014)
-- 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))`
+* 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))`
# 2.1.5 (6/2/2014)
-- fix issue with :single_org => <value> not being honored
+* fix issue with :single_org => <value> not being honored
# 2.1.4 (5/27/2014)
-- fix issue with global Thread.exit_on_exception being set
+* fix issue with global Thread.exit_on_exception being set
# 2.1.3 (5/27/2014)
-- 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
+* 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
# 2.1.2 (5/27/2014)
-- fix build_uri (and thus cookbook downloads)
+* fix build_uri (and thus cookbook downloads)
# 2.1.1 (5/26/2014)
-- flip defaults off in V1ToV2Adapater, allowing most chef tests to pass against 2.1.1
+* flip defaults off in V1ToV2Adapater, allowing most chef tests to pass against 2.1.1
# 2.1 (5/26/2014)
-- **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
+* **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
# 2.0.2 (1/20/2014)
-- 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
+* 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
# 2.0.1 (1/3/2014)
-- 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
+* 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
# 2.0.0 (12/17/2013)
-- Remove Puma (and `--socket` option)
-- Use a cleaner threading approach
-- Implement a better `running?` check
+* Remove Puma (and `--socket` option)
+* Use a cleaner threading approach
+* Implement a better `running?` check
# 1.7.3
-- (Backport) Read JSON, not a file path in `from_json`
+* (Backport) Read JSON, not a file path in `from_json`
# 1.6.3
-- (Backport) Read JSON, not a file path in `from_json`
+* (Backport) Read JSON, not a file path in `from_json`
# 1.5.5
-- Fix issue with - in term (name:a-b)
+* Fix issue with - in term (name:a-b)
# 1.5.4
-- Fix issue where run_lists in format cookbook::recipe@version do not depsolve
+* Fix issue where run_lists in format cookbook::recipe@version do not depsolve
# 1.5.3
-- Add Server: chef-zero header to response
+* Add Server: chef-zero header to response
# 1.5.2
-- Fix a couple of search query issues (make parentheses and NOT term:value work)
+* Fix a couple of search query issues (make parentheses and NOT term:value work)
# 1.5.1
-- Add Unix domain socket support (e.g. chef-zero --socket /tmp/chef-zero.sock) (stevendanna)
+* Add Unix domain socket support (e.g. chef-zero --socket /tmp/chef-zero.sock) (stevendanna)
# 1.5
-- Add -d option for daemon mode (sethvargo)
-- Fix bug with cookbook metadata.rb files that rely on __FILE__
+* Add -d option for daemon mode (sethvargo)
+* Fix bug with cookbook metadata.rb files that rely on __FILE__
# 1.4
-- Run with downgraded Puma 1.6 in order to work on Windows (2.x doesn't yet)
+* Run with downgraded Puma 1.6 in order to work on Windows (2.x doesn't yet)
# 1.3
-- Fix bug with search when JSON contains the same key in different places
+* Fix bug with search when JSON contains the same key in different places
# 1.2.1
-- Fix search when JSON contains integers
+* Fix search when JSON contains integers
# 1.2
-- Allow rspec users to specify cookbook NAME, VERSION, { :frozen => true }
-- Documentation fix
+* Allow rspec users to specify cookbook NAME, VERSION, { :frozen => true }
+* Documentation fix
# 1.1.3
-- Return better defaults for cookbooks
-- Support /cookbook_versions?cookbook_versions=... query parameter
-- Fix server crash when cookbook has multiple identical checksums
+* Return better defaults for cookbooks
+* Support /cookbook_versions?cookbook_versions=... query parameter
+* Fix server crash when cookbook has multiple identical checksums
# 1.1.2
-- Allow rspec users to specify the same data twice (overwrites)
+* Allow rspec users to specify the same data twice (overwrites)
# 1.1.1
-- Fix broken rspec functionality (jkeiser, reset)
+* Fix broken rspec functionality (jkeiser, reset)
# 1.1
-- Create plugin system to allow other storage besides memory
+* Create plugin system to allow other storage besides memory
# 1.0.1
-- Fix depsolver crash with frozen version strings (sethvargo)
+* Fix depsolver crash with frozen version strings (sethvargo)
# 1.0
-- Increased testing of server
+* Increased testing of server
# 0.9.13
-- Remove extra require of 'thin' so rspec users don't get broke
+* Remove extra require of 'thin' so rspec users don't get broke
# 0.9.12
-- Switch from thin to puma (sethvargo)
+* Switch from thin to puma (sethvargo)
# 0.9.11
-- Support full cookbook metadata.rb syntax, including "depends"
+* Support full cookbook metadata.rb syntax, including "depends"
# 0.9.10
-- Add -d flag to print debug output (sethvargo)
+* Add -d flag to print debug output (sethvargo)
# 0.9.9
-- Remove chef as a dependency so we can run on jruby (reset)
-- Server assumes json is acceptable if Accept header is not sent (stevendanna)
+* Remove chef as a dependency so we can run on jruby (reset)
+* Server assumes json is acceptable if Accept header is not sent (stevendanna)
# 0.9.8
-- Support runlists with a::b in them in depsolver
+* Support runlists with a::b in them in depsolver
# 0.9.7
-- Return file URLs and other important things in depsolver response
+* Return file URLs and other important things in depsolver response
# 0.9.6
-- Make 404 a JSON response
+* Make 404 a JSON response
# 0.9.5
-- Fix crash in 405 error response generator
-- Add ability to verify request/response pairs from rspec api
+* Fix crash in 405 error response generator
+* Add ability to verify request/response pairs from rspec api
# 0.9.4
-- Ruby 1.8.7 support
+* Ruby 1.8.7 support
# 0.9.3
-- rspec fixes:
+* 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
+* make 500 response return actual exception info
# 0.9.2
-- Speed increase for rspec (only start server once)
-- Support CTRL+C when running rspec chef-zero tests
+* Speed increase for rspec (only start server once)
+* Support CTRL+C when running rspec chef-zero tests
# 0.9.1
-- Switch from webrick -> thin
-- Bugfixes
+* Switch from webrick -> thin
+* Bugfixes
# 0.9
-- Initial code-complete release with working server
+* Initial code-complete release with working server
diff --git a/chef-zero.gemspec b/chef-zero.gemspec
index c6bc909..8538040 100644
--- a/chef-zero.gemspec
+++ b/chef-zero.gemspec
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
s.add_dependency 'mixlib-log', '~> 1.3'
s.add_dependency 'hashie', '~> 2.0'
s.add_dependency 'uuidtools', '~> 2.1'
- s.add_dependency 'ffi-yajl', '~> 1.1'
+ s.add_dependency 'ffi-yajl', '>= 1.1', '< 3.0'
s.add_dependency 'rack'
s.add_development_dependency 'rake'
diff --git a/lib/chef_zero/endpoints/version_endpoint.rb b/lib/chef_zero/endpoints/version_endpoint.rb
new file mode 100644
index 0000000..d38c33e
--- /dev/null
+++ b/lib/chef_zero/endpoints/version_endpoint.rb
@@ -0,0 +1,12 @@
+require 'chef_zero/rest_base'
+
+module ChefZero
+ module Endpoints
+ # /version
+ class VersionEndpoint < RestBase
+ def get(request)
+ text_response(200, "chef-zero #{ChefZero::VERSION}\n")
+ end
+ end
+ end
+end
diff --git a/lib/chef_zero/rest_base.rb b/lib/chef_zero/rest_base.rb
index 3fa017a..48d423a 100644
--- a/lib/chef_zero/rest_base.rb
+++ b/lib/chef_zero/rest_base.rb
@@ -181,6 +181,10 @@ module ChefZero
already_json_response(response_code, FFI_Yajl::Encoder.encode(json, :pretty => true))
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]
end
@@ -191,8 +195,9 @@ 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
- "#{base_uri}/#{rest_path[2..-1].join('/')}"
else
"#{base_uri}/#{rest_path.join('/')}"
end
diff --git a/lib/chef_zero/server.rb b/lib/chef_zero/server.rb
index de2a3f5..9cf7b39 100644
--- a/lib/chef_zero/server.rb
+++ b/lib/chef_zero/server.rb
@@ -27,6 +27,7 @@ require 'webrick'
require 'webrick/https'
require 'chef_zero'
+require 'chef_zero/socketless_server_map'
require 'chef_zero/chef_data/cookbook_data'
require 'chef_zero/chef_data/acl_path'
require 'chef_zero/rest_router'
@@ -83,9 +84,11 @@ require 'chef_zero/endpoints/user_association_request_endpoint'
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'
module ChefZero
class Server
+
DEFAULT_OPTIONS = {
:host => '127.0.0.1',
:port => 8889,
@@ -95,6 +98,11 @@ module ChefZero
:ssl => false
}.freeze
+ GLOBAL_ENDPOINTS = [
+ '/license',
+ '/version',
+ ]
+
def initialize(options = {})
@options = DEFAULT_OPTIONS.merge(options)
if @options[:single_org] && !@options.has_key?(:osc_compat)
@@ -102,6 +110,7 @@ module ChefZero
end
@options.freeze
ChefZero::Log.level = @options[:log_level].to_sym
+ @app = nil
end
# @return [Hash]
@@ -140,6 +149,12 @@ module ChefZero
end
end
+ def local_mode_url
+ raise "Port not yet set, cannot generate URL" unless port.kind_of?(Integer)
+ "chefzero://localhost:#{port}"
+ end
+
+
#
# The data store for this server (default is in-memory).
#
@@ -218,7 +233,6 @@ module ChefZero
thread.join
end
-
#
# Start a Chef Zero server in a forked process. This method returns the PID
# to the forked process.
@@ -278,9 +292,19 @@ module ChefZero
sleep(0.01)
end
+ SocketlessServerMap.instance.register_port(@port, self)
+
@thread
end
+ def start_socketless
+ @port = SocketlessServerMap.instance.register_no_listen_server(self)
+ end
+
+ def handle_socketless_request(request_env)
+ app.call(request_env)
+ end
+
#
# Boolean method to determine if the server is currently ready to accept
# requests. This method will attempt to make an HTTP request against the
@@ -309,6 +333,7 @@ module ChefZero
if @thread
ChefZero::Log.error("Chef Zero did not stop within #{wait} seconds! Killing...")
@thread.kill
+ SocketlessServerMap.deregister(port)
end
ensure
@server = nil
@@ -495,8 +520,7 @@ module ChefZero
[ "/organizations/*/*/*/_acl/*", AclEndpoint.new(self) ]
]
end
- result +
- [
+ result + [
# Both
[ "/organizations/*/clients", ActorsEndpoint.new(self) ],
[ "/organizations/*/clients/*", ActorEndpoint.new(self) ],
@@ -526,13 +550,21 @@ module ChefZero
[ "/organizations/*/sandboxes/*", SandboxEndpoint.new(self) ],
[ "/organizations/*/search", SearchesEndpoint.new(self) ],
[ "/organizations/*/search/*", SearchEndpoint.new(self) ],
+ [ "/version", VersionEndpoint.new(self) ],
# Internal
[ "/organizations/*/file_store/**", FileStoreFileEndpoint.new(self) ]
]
end
+ def global_endpoint?(ep)
+ GLOBAL_ENDPOINTS.any? do |g_ep|
+ ep.start_with?(g_ep)
+ end
+ end
+
def app
+ return @app if @app
router = RestRouter.new(open_source_endpoints)
router.not_found = NotFoundEndpoint.new
@@ -541,9 +573,11 @@ module ChefZero
else
rest_base_prefix = []
end
- return proc do |env|
+ @app = proc do |env|
begin
- request = RestRequest.new(env, rest_base_prefix)
+ prefix = global_endpoint?(env['PATH_INFO']) ? [] : rest_base_prefix
+
+ request = RestRequest.new(env, prefix)
if @on_request_proc
@on_request_proc.call(request)
end
@@ -577,6 +611,7 @@ module ChefZero
end
end
end
+ @app
end
def dejsonize_children(hash)
diff --git a/lib/chef_zero/socketless_server_map.rb b/lib/chef_zero/socketless_server_map.rb
new file mode 100644
index 0000000..531dbbb
--- /dev/null
+++ b/lib/chef_zero/socketless_server_map.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Daniel DeLeo (<dan@chef.io>)
+# Copyright:: Copyright (c) 2012 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require 'thread'
+require 'singleton'
+
+module ChefZero
+
+ class ServerNotFound < StandardError
+ end
+
+ class NoSocketlessPortAvailable < StandardError
+ end
+
+ class SocketlessServerMap
+
+ def self.request(port, request_env)
+ instance.request(port, request_env)
+ end
+
+ MUTEX = Mutex.new
+
+ include Singleton
+
+ def initialize()
+ reset!
+ end
+
+ def reset!
+ @servers_by_port = {}
+ end
+
+ def register_port(port, server)
+ MUTEX.synchronize do
+ @servers_by_port[port] = server
+ end
+ end
+
+ def register_no_listen_server(server)
+ MUTEX.synchronize do
+ 1.upto(1000) do |port|
+ unless @servers_by_port.key?(port)
+ @servers_by_port[port] = server
+ return port
+ end
+ end
+ raise NoSocketlessPortAvailable, "No socketless ports left to register"
+ end
+ end
+
+ def has_server_on_port?(port)
+ @servers_by_port.key?(port)
+ end
+
+ def deregister(port)
+ MUTEX.synchronize do
+ @servers_by_port.delete(port)
+ end
+ end
+
+ def request(port, request_env)
+ server = @servers_by_port[port]
+ raise ServerNotFound, "No socketless chef-zero server on given port #{port.inspect}" unless server
+ server.handle_socketless_request(request_env)
+ end
+
+ end
+end
+
diff --git a/lib/chef_zero/version.rb b/lib/chef_zero/version.rb
index 9235e9d..e1635a3 100644
--- a/lib/chef_zero/version.rb
+++ b/lib/chef_zero/version.rb
@@ -1,3 +1,3 @@
module ChefZero
- VERSION = '4.0'
+ VERSION = '4.1.0'
end
diff --git a/spec/server_spec.rb b/spec/server_spec.rb
index d5da281..558ef35 100644
--- a/spec/server_spec.rb
+++ b/spec/server_spec.rb
@@ -33,6 +33,12 @@ describe ChefZero::Server do
httpcall.get('/nodes', 'Accept' => accepts)
end
+ def get_version
+ uri = URI(@server.url)
+ httpcall = Net::HTTP.new(uri.host, uri.port)
+ httpcall.get('/version', 'Accept' => 'text/plain, application/json')
+ end
+
it 'accepts requests with no accept header' do
request = Net::HTTP::Get.new('/nodes')
request.delete('Accept')
@@ -77,6 +83,9 @@ describe ChefZero::Server do
expect(get_nodes('a/b;a=b;c=d, application/json;a=b, application/xml;a=b').code).to eq '200'
end
+ it 'accepts /version' do
+ expect(get_version.body.start_with?('chef-zero')).to be true
+ end
end
end
end
diff --git a/spec/socketless_server_map_spec.rb b/spec/socketless_server_map_spec.rb
new file mode 100644
index 0000000..8ef2ac7
--- /dev/null
+++ b/spec/socketless_server_map_spec.rb
@@ -0,0 +1,71 @@
+require 'chef_zero/socketless_server_map'
+
+
+describe "Socketless Mode" do
+
+ let(:server_map) { ChefZero::SocketlessServerMap.instance.tap { |i| i.reset! } }
+
+ let(:server) { instance_double("ChefZero::Server") }
+
+ let(:second_server) { instance_double("ChefZero::Server") }
+
+ it "registers a socketful server" do
+ server_map.register_port(8889, server)
+ expect(server_map).to have_server_on_port(8889)
+ end
+
+ context "when a no-listen server is registered" do
+
+ let!(:port) { server_map.register_no_listen_server(server) }
+
+ it "assigns the server a low port number" do
+ expect(port).to eq(1)
+ end
+
+ context "and another server is registered" do
+
+ let!(:next_port) { server_map.register_no_listen_server(second_server) }
+
+ it "assigns another port when another server is registered" do
+ expect(next_port).to eq(2)
+ end
+
+ it "raises NoSocketlessPortAvailable when too many servers are registered" do
+ expect { 1000.times { server_map.register_no_listen_server(server) } }.to raise_error(ChefZero::NoSocketlessPortAvailable)
+ end
+
+ it "deregisters a server" do
+ expect(server_map).to have_server_on_port(1)
+ server_map.deregister(1)
+ expect(server_map).to_not have_server_on_port(1)
+ end
+
+ describe "routing requests to a server" do
+
+ let(:rack_req) do
+ r = {}
+ r["REQUEST_METHOD"] = "GET"
+ r["SCRIPT_NAME"] = ""
+ r["PATH_INFO"] = "/clients"
+ r["QUERY_STRING"] = ""
+ r["rack.input"] = StringIO.new("")
+ r
+ end
+
+ let(:rack_response) { [200, {}, ["this is the response body"] ] }
+
+ it "routes a request to the registered port" do
+ expect(server).to receive(:handle_socketless_request).with(rack_req).and_return(rack_response)
+ response = server_map.request(1, rack_req)
+ expect(response).to eq(rack_response)
+ end
+
+ it "raises ServerNotFound when a request is sent to an unregistered port" do
+ expect { server_map.request(99, rack_req) }.to raise_error(ChefZero::ServerNotFound)
+ end
+ end
+ end
+ end
+
+
+end