diff options
-rw-r--r-- | Rakefile | 4 | ||||
-rw-r--r-- | chef-server-api/app/controllers/cookbooks.rb | 69 | ||||
-rw-r--r-- | chef-server-api/app/helpers/tarball_helper.rb | 82 | ||||
-rw-r--r-- | chef/lib/chef/config.rb | 3 | ||||
-rw-r--r-- | chef/lib/chef/cookbook_loader.rb | 2 | ||||
-rw-r--r-- | chef/lib/chef/rest.rb | 9 | ||||
-rw-r--r-- | chef/spec/unit/cookbook_loader_spec.rb | 4 | ||||
-rw-r--r-- | cucumber.yml | 1 | ||||
-rw-r--r-- | features/api/cookbooks/manage_cookbooks.feature | 214 | ||||
-rw-r--r-- | features/data/config/server.rb | 39 | ||||
-rw-r--r-- | features/data/cookbook_tarballs/empty_tarball.tar.gz | bin | 0 -> 116 bytes | |||
-rw-r--r-- | features/data/cookbook_tarballs/new.tar.gz | bin | 0 -> 161 bytes | |||
-rw-r--r-- | features/data/cookbook_tarballs/not_a_tarball.txt | 1 | ||||
-rw-r--r-- | features/data/cookbook_tarballs/original.tar.gz | bin | 0 -> 161 bytes | |||
-rw-r--r-- | features/steps/cookbook_steps.rb | 73 | ||||
-rw-r--r-- | features/steps/fixture_steps.rb | 24 | ||||
-rw-r--r-- | features/support/env.rb | 4 |
17 files changed, 499 insertions, 30 deletions
@@ -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 Binary files differnew file mode 100644 index 0000000000..0834ddfe17 --- /dev/null +++ b/features/data/cookbook_tarballs/empty_tarball.tar.gz diff --git a/features/data/cookbook_tarballs/new.tar.gz b/features/data/cookbook_tarballs/new.tar.gz Binary files differnew file mode 100644 index 0000000000..71f27defdd --- /dev/null +++ b/features/data/cookbook_tarballs/new.tar.gz 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 Binary files differnew file mode 100644 index 0000000000..149fd262b1 --- /dev/null +++ b/features/data/cookbook_tarballs/original.tar.gz 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") |