summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Rakefile4
-rw-r--r--chef-server-api/app/controllers/cookbooks.rb69
-rw-r--r--chef-server-api/app/helpers/tarball_helper.rb82
-rw-r--r--chef/lib/chef/config.rb3
-rw-r--r--chef/lib/chef/cookbook_loader.rb2
-rw-r--r--chef/lib/chef/rest.rb9
-rw-r--r--chef/spec/unit/cookbook_loader_spec.rb4
-rw-r--r--cucumber.yml1
-rw-r--r--features/api/cookbooks/manage_cookbooks.feature214
-rw-r--r--features/data/config/server.rb39
-rw-r--r--features/data/cookbook_tarballs/empty_tarball.tar.gzbin0 -> 116 bytes
-rw-r--r--features/data/cookbook_tarballs/new.tar.gzbin0 -> 161 bytes
-rw-r--r--features/data/cookbook_tarballs/not_a_tarball.txt1
-rw-r--r--features/data/cookbook_tarballs/original.tar.gzbin0 -> 161 bytes
-rw-r--r--features/steps/cookbook_steps.rb73
-rw-r--r--features/steps/fixture_steps.rb24
-rw-r--r--features/support/env.rb4
17 files changed, 499 insertions, 30 deletions
diff --git a/Rakefile b/Rakefile
index 72b45f408a..fcfa40b221 100644
--- a/Rakefile
+++ b/Rakefile
@@ -290,6 +290,10 @@ namespace :features do
Cucumber::Rake::Task.new(:cookbooks) do |t|
t.profile = "api_cookbooks"
end
+
+ Cucumber::Rake::Task.new(:cookbook_tarballs) do |t|
+ t.profile = "api_cookbooks_tarballs"
+ end
end
namespace :data do
diff --git a/chef-server-api/app/controllers/cookbooks.rb b/chef-server-api/app/controllers/cookbooks.rb
index a5caba8078..7176b8d855 100644
--- a/chef-server-api/app/controllers/cookbooks.rb
+++ b/chef-server-api/app/controllers/cookbooks.rb
@@ -1,7 +1,8 @@
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Author:: Christopher Brown (<cb@opscode.com>)
-# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -27,6 +28,7 @@ class ChefServerApi::Cookbooks < ChefServerApi::Application
before :authenticate_every
include Chef::Mixin::Checksum
+ include Merb::ChefServerApi::TarballHelper
def index
cl = Chef::CookbookLoader.new
@@ -42,7 +44,7 @@ class ChefServerApi::Cookbooks < ChefServerApi::Application
begin
cookbook = cl[params[:id]]
rescue ArgumentError => e
- raise NotFound, "Cannot find a cookbook named #{cookbook.to_s}"
+ raise NotFound, "Cannot find a cookbook named #{params[:id]}"
end
results = load_cookbook_files(cookbook)
results[:name] = cookbook.name.to_s
@@ -50,7 +52,7 @@ class ChefServerApi::Cookbooks < ChefServerApi::Application
display results
end
- def show_segment
+ def show_segment
cl = Chef::CookbookLoader.new
begin
cookbook = cl[params[:cookbook_id]]
@@ -125,5 +127,66 @@ class ChefServerApi::Cookbooks < ChefServerApi::Application
end
end
+ def create
+ # validate name and file parameters and throw an error if a cookbook with the same name already exists
+ raise BadRequest, "missing required parameter: name" unless params[:name]
+ desired_name = params[:name]
+ raise BadRequest, "invalid parameter: name must be at least one character long and contain only letters, numbers, periods (.), underscores (_), and hyphens (-)" unless desired_name =~ /\A[\w.-]+\Z/
+ begin
+ validate_file_parameter(desired_name, params[:file])
+ rescue FileParameterException => te
+ raise BadRequest, te.message
+ end
+
+ begin
+ Chef::CookbookLoader.new[desired_name]
+ raise BadRequest, "Cookbook with the name #{desired_name} already exists"
+ rescue ArgumentError
+ end
+
+ expand_tarball_and_put_in_repository(desired_name, params[:file][:tempfile])
+
+ # construct successful response
+ self.status = 201
+ location = absolute_slice_url(:cookbook, :id => desired_name)
+ headers['Location'] = location
+ result = { 'uri' => location }
+ display result
+ end
+
+ def get_tarball
+ cookbook_name = params[:cookbook_id]
+ expected_location = cookbook_location(cookbook_name)
+ raise NotFound, "Cannot find cookbook named #{cookbook_name} at #{expected_location}. Note: Tarball generation only applies to cookbooks under the first directory in the server's Chef::Config.cookbook_path variable and does to apply overrides." unless File.directory? expected_location
+
+ send_file(get_or_create_cookbook_tarball_location(cookbook_name))
+ end
+
+ def update
+ cookbook_name = params[:cookbook_id]
+ cookbook_path = cookbook_location(cookbook_name)
+ raise NotFound, "Cannot find cookbook named #{cookbook_name}" unless File.directory? cookbook_path
+ begin
+ validate_file_parameter(cookbook_name, params[:file])
+ rescue FileParameterException => te
+ raise BadRequest, te.message
+ end
+
+ expand_tarball_and_put_in_repository(cookbook_name, params[:file][:tempfile])
+
+ display Hash.new
+ end
+
+ def destroy
+ cookbook_name = params[:id]
+ cookbook_path = cookbook_location(cookbook_name)
+ raise NotFound, "Cannot find cookbook named #{cookbook_name}" unless File.directory? cookbook_path
+
+ FileUtils.rm_rf(cookbook_path)
+ FileUtils.rm_f(cookbook_tarball_location(cookbook_name))
+
+ display Hash.new
+ end
+
end
diff --git a/chef-server-api/app/helpers/tarball_helper.rb b/chef-server-api/app/helpers/tarball_helper.rb
new file mode 100644
index 0000000000..f487d0b80b
--- /dev/null
+++ b/chef-server-api/app/helpers/tarball_helper.rb
@@ -0,0 +1,82 @@
+#
+# Author:: Christopher Walters (<cw@opscode.com>)
+# Copyright:: Copyright (c) 2009 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.
+#
+
+module Merb
+ module ChefServerApi
+ module TarballHelper
+
+ class FileParameterException < StandardError ; end
+
+ def validate_file_parameter(cookbook_name, file_param)
+ raise FileParameterException, "missing required parameter: file" unless file_param
+ raise FileParameterException, "invalid parameter: file must be a File" unless file_param.respond_to?(:has_key?) && file_param[:tempfile].respond_to?(:read)
+ tarball_path = file_param[:tempfile].path
+ raise FileParameterException, "invalid tarball: (try creating with 'tar czf cookbook.tar.gz cookbook/')" unless system("tar", "tzf", tarball_path)
+ entry_roots = `tar tzf #{tarball_path}`.split("\n").map{|e|e.split('/').first}.uniq
+ raise FileParameterException, "invalid tarball: tarball root must contain #{cookbook_name}" unless entry_roots.include?(cookbook_name)
+ end
+
+ def cookbook_base
+ [Chef::Config.cookbook_path].flatten.first
+ end
+
+ def cookbook_location(cookbook_name)
+ File.join(cookbook_base, cookbook_name)
+ end
+
+ def cookbook_tarball_location(cookbook_name)
+ File.join(Chef::Config.cookbook_tarball_path, "#{cookbook_name}.tar.gz")
+ end
+
+ def get_or_create_cookbook_tarball_location(cookbook_name)
+ tarball_location = cookbook_tarball_location(cookbook_name)
+ unless File.exists? tarball_location
+ args = ["tar", "-C", cookbook_base, "-czf", tarball_location, cookbook_name]
+ Chef::Log.debug("Tarball for #{cookbook_name} not found, so creating at #{tarball_location} with '#{args.join(' ')}'")
+ FileUtils.mkdir_p(Chef::Config.cookbook_tarball_path)
+ system(*args)
+ end
+ tarball_location
+ end
+
+ def expand_tarball_and_put_in_repository(cookbook_name, file)
+ # untar cookbook tarball into tempdir
+ tempdir = File.join("#{file.path}.data")
+ Chef::Log.debug("Creating #{tempdir} and untarring #{file.path} into it")
+ FileUtils.mkdir_p(tempdir)
+ raise "Could not untar file" unless system("tar", "xzf", file.path, "-C", tempdir)
+
+ cookbook_path = cookbook_location(cookbook_name)
+ tarball_path = cookbook_tarball_location(cookbook_name)
+
+ # clear any existing cookbook components and move tempdir into the repository
+ Chef::Log.debug("Moving #{tempdir} to #{cookbook_path}")
+ FileUtils.rm_rf(cookbook_path)
+ FileUtils.mkdir_p(cookbook_path)
+ Dir[File.join(tempdir, cookbook_name, "*")].each{|e| FileUtils.mv(e, cookbook_path)}
+
+ # clear the existing tarball (if exists) and move the downloaded tarball to the cache
+ Chef::Log.debug("Moving #{file.path} to #{tarball_path}")
+ FileUtils.mkdir_p(Chef::Config.cookbook_tarball_path)
+ FileUtils.rm_f(tarball_path)
+ FileUtils.mv(file.path, tarball_path)
+ end
+
+ end
+ end
+end
diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb
index b433e31e00..5121bc445a 100644
--- a/chef/lib/chef/config.rb
+++ b/chef/lib/chef/config.rb
@@ -98,7 +98,8 @@ class Chef
authorized_openid_identifiers nil
authorized_openid_providers nil
- cookbook_path [ "/var/chef/site-cookbooks", "/var/chef/cookbooks" ]
+ cookbook_path [ "/var/chef/cookbooks", "/var/chef/site-cookbooks" ]
+ cookbook_tarballs_path "/var/chef/cookbook-tarballs"
couchdb_database "chef"
couchdb_url "http://localhost:5984"
couchdb_version nil
diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb
index c6cc612306..6e4657d9ea 100644
--- a/chef/lib/chef/cookbook_loader.rb
+++ b/chef/lib/chef/cookbook_loader.rb
@@ -38,7 +38,7 @@ class Chef
def load_cookbooks
cookbook_settings = Hash.new
- [Chef::Config.cookbook_path].flatten.reverse.each do |cb_path|
+ [Chef::Config.cookbook_path].flatten.each do |cb_path|
Dir[File.join(cb_path, "*")].each do |cookbook|
next unless File.directory?(cookbook)
cookbook_name = File.basename(cookbook).to_sym
diff --git a/chef/lib/chef/rest.rb b/chef/lib/chef/rest.rb
index eae9287a6f..d3640c2395 100644
--- a/chef/lib/chef/rest.rb
+++ b/chef/lib/chef/rest.rb
@@ -36,14 +36,15 @@ class Chef
include Singleton
end
- attr_accessor :url, :cookies, :signing_key
+ attr_accessor :url, :cookies, :client_name, :signing_key, :signing_key_filename
- def initialize(url, client_name=Chef::Config[:node_name], signing_key=Chef::Config[:client_key])
+ def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key])
@url = url
@cookies = CookieJar.instance
@client_name = client_name
- if signing_key
- @signing_key = load_signing_key(signing_key)
+ if signing_key_filename
+ @signing_key_filename = signing_key_filename
+ @signing_key = load_signing_key(signing_key_filename)
else
@signing_key = nil
end
diff --git a/chef/spec/unit/cookbook_loader_spec.rb b/chef/spec/unit/cookbook_loader_spec.rb
index b1a679ab0d..1c5a89785b 100644
--- a/chef/spec/unit/cookbook_loader_spec.rb
+++ b/chef/spec/unit/cookbook_loader_spec.rb
@@ -21,8 +21,8 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
describe Chef::CookbookLoader do
before(:each) do
Chef::Config.cookbook_path [
- File.join(File.dirname(__FILE__), "..", "data", "cookbooks"),
- File.join(File.dirname(__FILE__), "..", "data", "kitchen")
+ File.join(File.dirname(__FILE__), "..", "data", "kitchen"),
+ File.join(File.dirname(__FILE__), "..", "data", "cookbooks")
]
@cl = Chef::CookbookLoader.new()
end
diff --git a/cucumber.yml b/cucumber.yml
index 43d77cbd5e..227d9521ea 100644
--- a/cucumber.yml
+++ b/cucumber.yml
@@ -1,6 +1,7 @@
default: -f pretty features -r features/steps -r features/support
api: --tags @api --format pretty -r features/steps -r features/support features
api_cookbooks: --tags @api,@cookbooks --format pretty -r features/steps -r features/support features
+api_cookbooks_tarballs: --tags @api,@cookbooks,@tarballs --format pretty -r features/steps -r features/support features
api_clients: --tags @api_clients --format pretty -r features/steps -r features/support features
api_clients_create: --tags clients_create --format pretty -r features/steps -r features/support features
api_clients_delete: --tags clients_delete --format pretty -r features/steps -r features/support features
diff --git a/features/api/cookbooks/manage_cookbooks.feature b/features/api/cookbooks/manage_cookbooks.feature
new file mode 100644
index 0000000000..cb9102ab28
--- /dev/null
+++ b/features/api/cookbooks/manage_cookbooks.feature
@@ -0,0 +1,214 @@
+@api @cookbooks @tarballs
+@manage_cookbook_tarballs
+
+Feature: CRUD cookbook tarballs
+ In order to manage cookbook data
+ As a Developer
+ I want to create, read, update, and delete cookbook tarballs
+
+ # Creating a cookbook -- negative
+@create_cookbook_negative
+ Scenario: Should not be able to create a cookbook without a name parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ When I create a cookbook with 'original cookbook tarball'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'missing required parameter: name'
+
+ Scenario: Should not be able to create a cookbook with a blank name parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ When I create a cookbook named '' with 'original cookbook tarball'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'invalid parameter: name'
+
+ Scenario: Should not be able to create a cookbook with invalid characters in the name parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ When I create a cookbook named 'a+b' with 'original cookbook tarball'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'invalid parameter: name'
+
+ Scenario: Should not be able to create a cookbook with a name that already exists
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'new cookbook tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I create a cookbook named 'test_cookbook' with 'new cookbook tarball'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'cookbook already exists'
+
+ # TODO: when CHEF-780 is resolved, uncomment the 400 response expectation and remove the 401 expectation. Note: when authenticate_every is commented out and the 400 is expected, it behaves properly
+ Scenario: Should not be able to create a cookbook without a file parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'hash' named 'nothing'
+ When I create a cookbook named 'test_cookbook' with 'nothing'
+# Then the response code should be '400'
+# And the inflated responses key 'error' should include 'missing required parameter: file'
+ Then the response code should be '401'
+
+ # TODO: when CHEF-780 is resolved, uncomment the 400 response expectation and remove the 401 expectation. Note: when authenticate_every is commented out and the 400 is expected, it behaves properly
+@manage_cookbook_tarballs_blank_file
+ Scenario: Should not be able to create a cookbook with a blank file parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'blank file parameter'
+ When I create a cookbook named 'test_cookbook' with 'blank file parameter'
+# Then the response code should be '400'
+# And the inflated responses key 'error' should include 'invalid parameter: file must be a File'
+ Then the response code should be '401'
+
+ # TODO: when CHEF-780 is resolved, uncomment the 400 response expectation and remove the 401 expectation. Note: when authenticate_every is commented out and the 400 is expected, it behaves properly
+@manage_cookbook_tarballs_string_file
+ Scenario: Should not be able to create a cookbook with a string file parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'string file parameter'
+ When I create a cookbook named 'test_cookbook' with 'string file parameter'
+# Then the response code should be '400'
+# And the inflated responses key 'error' should include 'invalid parameter: file must be a File'
+ Then the response code should be '401'
+
+ Scenario: Should not be able to create a cookbook with an invalid tarball
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'not a tarball'
+ When I create a cookbook named 'test_cookbook' with 'not a tarball'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'invalid tarball'
+
+ Scenario: Should not be able to create a cookbook with a tarball that does not contain a directory in the base with the same name as the cookbook
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'empty tarball'
+ When I create a cookbook named 'test_cookbook' with 'empty tarball'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'invalid tarball'
+
+ # Creating a cookbook -- positive
+
+@create_cookbook_positive
+ Scenario: Create a cookbook and verify that it is in proceeding cookbooks listings
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ When I create a cookbook named 'test_cookbook' with 'original cookbook tarball'
+ Then the response code should be '201'
+ And the 'Location' header should match 'http://[^/]+/cookbooks/test_cookbook'
+ And the inflated responses key 'uri' should match 'http://[^/]+/cookbooks/test_cookbook'
+ When I 'GET' the path '/cookbooks'
+ Then the inflated responses key 'test_cookbook' should exist
+
+ # Downloading a cookbook -- negative
+
+ Scenario: Downloading a non-existent cookbook should fail
+ Given a 'registration' named 'bobo' exists
+ When I download the 'non_existent' cookbook
+ Then I should get a '404 "Not Found"' exception
+
+ # Downloading a cookbook -- positive
+
+ Scenario: After a cookbook is uploaded, it should be downloadable
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I download the 'test_cookbook' cookbook
+ Then the response should be a valid tarball
+ And the untarred response should include file 'test_cookbook/original'
+
+ Scenario: Should be able to download a tarball for a cookbook that was placed on the file system (not uploaded through the API)
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ And I delete the cached tarball for 'test_cookbook'
+ When I download the 'test_cookbook' cookbook
+ Then the response should be a valid tarball
+ And the untarred response should include file 'test_cookbook/original'
+
+ # Updating a cookbook -- negative
+
+ Scenario: Updating a non-existent cookbook should fail
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ When I upload 'original cookbook tarball' to cookbook 'test_cookbook'
+ Then the response code should be '404'
+
+ Scenario: Should not be able to update a cookbook without a file parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'hash' named 'nothing'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I upload 'nothing' to cookbook 'test_cookbook'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'missing required parameter: file'
+
+ # TODO: when CHEF-780 is resolved, uncomment the 400 response expectation and remove the 401 expectation. Note: when authenticate_every is commented out and the 400 is expected, it behaves properly
+ Scenario: Should not be able to update a cookbook with a blank file parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'blank file parameter'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I upload 'blank file parameter' to cookbook 'test_cookbook'
+# Then the response code should be '400'
+# And the inflated responses key 'error' should include 'invalid parameter: file must be a File'
+ Then the response code should be '401'
+
+ # TODO: when CHEF-780 is resolved, uncomment the 400 response expectation and remove the 401 expectation. Note: when authenticate_every is commented out and the 400 is expected, it behaves properly
+ Scenario: Should not be able to update a cookbook with a string file parameter
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'string file parameter'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I upload 'string file parameter' to cookbook 'test_cookbook'
+# Then the response code should be '400'
+# And the inflated responses key 'error' should include 'invalid parameter: file must be a File'
+ Then the response code should be '401'
+
+ Scenario: Should not be able to update a cookbook with an invalid tarball
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'not a tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I upload 'not a tarball' to cookbook 'test_cookbook'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'invalid tarball'
+
+ Scenario: Should not be able to update a cookbook with a tarball that does not contain a directory in the base with the same name as the cookbook
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'empty tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I upload 'empty tarball' to cookbook 'test_cookbook'
+ Then the response code should be '400'
+ And the inflated responses key 'error' should include 'invalid tarball'
+
+ # Updating a cookbook -- positive
+
+ Scenario: Should be able to update a cookbook and download the latest version afterwards
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'new cookbook tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I upload 'new cookbook tarball' to cookbook 'test_cookbook'
+ Then the response code should be '200'
+ When I download the 'test_cookbook' cookbook
+ And the response should be a valid tarball
+ And the untarred response should include file 'test_cookbook/new'
+
+ # Deleting a cookbook -- negative
+
+ Scenario: Deleting a non-existent cookbook should fail
+ Given a 'registration' named 'bobo' exists
+ When I delete cookbook 'non_existent'
+ Then I should get a '404 "Not Found"' exception
+
+ # Deleting a cookbook -- positive
+
+ Scenario: Should be able to delete a cookbook and should be able to create a cookbook with the same name afterwards
+ Given a 'registration' named 'bobo' exists
+ And a 'file' named 'original cookbook tarball'
+ And a 'file' named 'new cookbook tarball'
+ And a cookbook named 'test_cookbook' is created with 'original cookbook tarball'
+ When I delete cookbook 'test_cookbook'
+ When I download the 'test_cookbook' cookbook
+ Then I should get a '404 "Not Found"' exception
+ When I create a cookbook named 'test_cookbook' with 'new cookbook tarball'
+ Then the response code should be '201'
+ When I download the 'test_cookbook' cookbook
+ And the response should be a valid tarball
+ And the untarred response should include file 'test_cookbook/new'
diff --git a/features/data/config/server.rb b/features/data/config/server.rb
index 7f8bac9a33..b6489ee7d9 100644
--- a/features/data/config/server.rb
+++ b/features/data/config/server.rb
@@ -1,25 +1,26 @@
supportdir = File.expand_path(File.join(File.dirname(__FILE__), ".."))
tmpdir = File.expand_path(File.join(File.dirname(__FILE__), "..", "tmp"))
-log_level :debug
-log_location STDOUT
-file_cache_path File.join(tmpdir, "cache")
-ssl_verify_mode :verify_none
-registration_url "http://127.0.0.1:4000"
-openid_url "http://127.0.0.1:4000"
-template_url "http://127.0.0.1:4000"
-remotefile_url "http://127.0.0.1:4000"
-search_url "http://127.0.0.1:4000"
-role_url "http://127.0.0.1:4000"
-chef_server_url "http://127.0.0.1:4000"
-client_url "http://127.0.0.1:4000"
-cookbook_path File.join(supportdir, "cookbooks")
-openid_store_path File.join(tmpdir, "openid", "store")
-openid_cstore_path File.join(tmpdir, "openid", "cstore")
-search_index_path File.join(tmpdir, "search_index")
-role_path File.join(supportdir, "roles")
-signing_ca_path File.join(tmpdir, "ca")
-couchdb_database 'chef_integration'
+log_level :debug
+log_location STDOUT
+file_cache_path File.join(tmpdir, "cache")
+ssl_verify_mode :verify_none
+registration_url "http://127.0.0.1:4000"
+openid_url "http://127.0.0.1:4000"
+template_url "http://127.0.0.1:4000"
+remotefile_url "http://127.0.0.1:4000"
+search_url "http://127.0.0.1:4000"
+role_url "http://127.0.0.1:4000"
+chef_server_url "http://127.0.0.1:4000"
+client_url "http://127.0.0.1:4000"
+cookbook_path [File.join(tmpdir, "cookbooks"), File.join(supportdir, "cookbooks")]
+cookbook_tarball_path File.join(tmpdir, "cookbook-tarballs")
+openid_store_path File.join(tmpdir, "openid", "store")
+openid_cstore_path File.join(tmpdir, "openid", "cstore")
+search_index_path File.join(tmpdir, "search_index")
+role_path File.join(supportdir, "roles")
+signing_ca_path File.join(tmpdir, "ca")
+couchdb_database 'chef_integration'
systmpdir = File.expand_path(File.join(Dir.tmpdir, "chef_integration"))
diff --git a/features/data/cookbook_tarballs/empty_tarball.tar.gz b/features/data/cookbook_tarballs/empty_tarball.tar.gz
new file mode 100644
index 0000000000..0834ddfe17
--- /dev/null
+++ b/features/data/cookbook_tarballs/empty_tarball.tar.gz
Binary files differ
diff --git a/features/data/cookbook_tarballs/new.tar.gz b/features/data/cookbook_tarballs/new.tar.gz
new file mode 100644
index 0000000000..71f27defdd
--- /dev/null
+++ b/features/data/cookbook_tarballs/new.tar.gz
Binary files differ
diff --git a/features/data/cookbook_tarballs/not_a_tarball.txt b/features/data/cookbook_tarballs/not_a_tarball.txt
new file mode 100644
index 0000000000..b8c3637dec
--- /dev/null
+++ b/features/data/cookbook_tarballs/not_a_tarball.txt
@@ -0,0 +1 @@
+I'm not a tarball.
diff --git a/features/data/cookbook_tarballs/original.tar.gz b/features/data/cookbook_tarballs/original.tar.gz
new file mode 100644
index 0000000000..149fd262b1
--- /dev/null
+++ b/features/data/cookbook_tarballs/original.tar.gz
Binary files differ
diff --git a/features/steps/cookbook_steps.rb b/features/steps/cookbook_steps.rb
index 1c5c0276ae..521c4546da 100644
--- a/features/steps/cookbook_steps.rb
+++ b/features/steps/cookbook_steps.rb
@@ -53,3 +53,76 @@ When /^I run the rake task to generate cookbook metadata$/ do
end
end
+#####
+# Cookbook tarball-specific steps
+#####
+
+require 'chef/streaming_cookbook_uploader'
+
+Given /^a cookbook named '(.+?)' is created with '(.*?)'$/ do |cookbook, stash_key|
+ params = {:name => cookbook}.merge(@stash[stash_key])
+ response = Chef::StreamingCookbookUploader.post("#{Chef::Config[:chef_server_url]}/cookbooks", rest.client_name, rest.signing_key_filename, params)
+ response.status.should == 201
+end
+
+When /^I delete the cached tarball for '(.*?)'$/ do |cookbook|
+ path = File.join(server_tmpdir, "cookbook-tarballs", "#{cookbook}.tar.gz")
+ Chef::Log.debug "Deleting #{path}"
+ FileUtils.rm_f(path)
+end
+
+When /^I create a cookbook(?: named '(.*?)')? with '(.*?)'$/ do |name, stash_key|
+ payload = { }
+ payload[:name] = name if name
+ payload.merge!(@stash[stash_key])
+ url = "#{Chef::Config[:chef_server_url]}/cookbooks"
+ payload[:file].rewind if payload[:file].kind_of?(File)
+ response = Chef::StreamingCookbookUploader.post(url, rest.client_name, rest.signing_key_filename, payload)
+
+ store_response response
+end
+
+When /^I upload '(.*?)' to cookbook '(.*?)'$/ do |stash_key, cookbook|
+ payload = @stash[stash_key]
+ url = "#{Chef::Config[:chef_server_url]}/cookbooks/#{cookbook}/_content"
+ payload[:file].rewind if payload[:file].kind_of?(File)
+ response = Chef::StreamingCookbookUploader.put(url, rest.client_name, rest.signing_key_filename, payload)
+
+ store_response response
+end
+
+When /^I download the '(.*?)' cookbook$/ do |cookbook|
+ When "I 'GET' the path '/cookbooks/#{cookbook}/_content'"
+end
+
+When /^I delete cookbook '(.*?)'$/ do |cookbook|
+ When "I 'DELETE' the path '/cookbooks/#{cookbook}'"
+end
+
+Then /^the response should be a valid tarball$/ do
+ Tempfile.open('tarball') do |tempfile|
+ tempfile.write(self.response.to_s)
+ tempfile.flush()
+ system("tar", "tzf", tempfile.path).should == true
+ end
+end
+
+Then /^the untarred response should include file '(.+)'$/ do |filepath|
+ Tempfile.open('tarball') do |tempfile|
+ tempfile.write(self.response.to_s)
+ tempfile.flush()
+ `tar tzf #{tempfile.path}`.split("\n").select{|e| e == filepath}.empty?.should == false
+ end
+end
+
+def store_response(resp)
+ self.response = resp
+
+ STDERR.puts "store response: #{resp.inspect}, #{resp.to_s}"# if ENV['DEBUG']=='true'
+ begin
+ STDERR.puts resp.to_s
+ self.inflated_response = JSON.parse(resp.to_s)
+ rescue
+ STDERR.puts "failed to convert response to JSON: #{$!.message}" if ENV['DEBUG']=='true'
+ end
+end
diff --git a/features/steps/fixture_steps.rb b/features/steps/fixture_steps.rb
index b3ae543c8b..00b88afe70 100644
--- a/features/steps/fixture_steps.rb
+++ b/features/steps/fixture_steps.rb
@@ -122,6 +122,30 @@ Before do
n.run_list << "node_cookbook_sync"
n
end
+ },
+ 'hash' => {
+ 'nothing' => Hash.new,
+ 'name only' => { :name => 'test_cookbook' }
+ },
+ 'file' => {
+ 'blank file parameter' => {
+ :file => nil
+ },
+ 'string file parameter' => {
+ :file => "just some text"
+ },
+ 'original cookbook tarball' => {
+ :file => File.new(File.join(datadir, "cookbook_tarballs", "original.tar.gz"), 'rb')
+ },
+ 'new cookbook tarball' => {
+ :file => File.new(File.join(datadir, "cookbook_tarballs", "new.tar.gz"), 'rb')
+ },
+ 'not a tarball' => {
+ :file => File.new(File.join(datadir, "cookbook_tarballs", "not_a_tarball.txt"), 'rb')
+ },
+ 'empty tarball' => {
+ :file => File.new(File.join(datadir, "cookbook_tarballs", "empty_tarball.tar.gz"), 'rb')
+ }
}
}
@stash = {}
diff --git a/features/support/env.rb b/features/support/env.rb
index e368348051..b8aafd7483 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -151,6 +151,10 @@ module ChefWorld
def tmpdir
@tmpdir ||= File.join(Dir.tmpdir, "chef_integration")
end
+
+ def server_tmpdir
+ @server_tmpdir ||= File.expand_path(File.join(datadir, "tmp"))
+ end
def datadir
@datadir ||= File.join(File.dirname(__FILE__), "..", "data")