summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--chef-server-api/app/controllers/cookbooks.rb6
-rw-r--r--chef/lib/chef/cookbook_loader.rb3
-rw-r--r--chef/lib/chef/cookbook_uploader.rb21
-rw-r--r--chef/lib/chef/cookbook_version.rb25
-rw-r--r--chef/lib/chef/exceptions.rb3
-rw-r--r--chef/lib/chef/knife/cookbook_upload.rb118
-rw-r--r--chef/spec/unit/cookbook_loader_spec.rb2
-rw-r--r--chef/spec/unit/cookbook_version_spec.rb9
8 files changed, 149 insertions, 38 deletions
diff --git a/chef-server-api/app/controllers/cookbooks.rb b/chef-server-api/app/controllers/cookbooks.rb
index d04eb0ed90..c109dadf87 100644
--- a/chef-server-api/app/controllers/cookbooks.rb
+++ b/chef-server-api/app/controllers/cookbooks.rb
@@ -119,6 +119,12 @@ class Cookbooks < Application
cookbook = params['inflated_object']
end
+ if cookbook.frozen_version? && params[:force].nil?
+ raise Conflict, "The cookbook #{cookbook.name} at version #{cookbook.version} is frozen. Use the 'force' option to override."
+ end
+
+ cookbook.freeze_version if params["inflated_object"].frozen_version?
+
# ensure that all checksums referred to by the manifest have been uploaded.
Chef::CookbookVersion::COOKBOOK_SEGMENTS.each do |segment|
next unless cookbook.manifest[segment]
diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb
index 56bb9092a5..463dafbd7d 100644
--- a/chef/lib/chef/cookbook_loader.rb
+++ b/chef/lib/chef/cookbook_loader.rb
@@ -19,6 +19,7 @@
# limitations under the License.
require 'chef/config'
+require 'chef/exceptions'
require 'chef/cookbook/cookbook_version_loader'
require 'chef/cookbook_version'
require 'chef/cookbook/chefignore'
@@ -71,7 +72,7 @@ class Chef
if @cookbooks_by_name.has_key?(cookbook.to_sym)
@cookbooks_by_name[cookbook.to_sym]
else
- raise ArgumentError, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (http://wiki.opscode.com/display/chef/Metadata)"
+ raise Exceptions::CookbookNotFoundInRepo, "Cannot find a cookbook named #{cookbook.to_s}; did you forget to add metadata to a cookbook? (http://wiki.opscode.com/display/chef/Metadata)"
end
end
diff --git a/chef/lib/chef/cookbook_uploader.rb b/chef/lib/chef/cookbook_uploader.rb
index 20c21cfd69..72f45c5ad1 100644
--- a/chef/lib/chef/cookbook_uploader.rb
+++ b/chef/lib/chef/cookbook_uploader.rb
@@ -1,5 +1,5 @@
require 'rest_client'
-require 'chef/cookbook_loader'
+require 'chef/exceptions'
require 'chef/checksum_cache'
require 'chef/sandbox'
require 'chef/cookbook_version'
@@ -11,9 +11,21 @@ class Chef
attr_reader :cookbook
attr_reader :path
+ attr_reader :opts
- def initialize(cookbook, path)
- @cookbook, @path = cookbook, path
+ # Creates a new CookbookUploader.
+ # ===Arguments:
+ # * cookbook::: A Chef::CookbookVersion describing the cookbook to be uploaded
+ # * path::: A String or Array of Strings representing the base paths to the
+ # cookbook repositories.
+ # * opts::: (optional) An options Hash
+ # ===Options:
+ # * :force indicates that the uploader should set the force option when
+ # uploading the cookbook. This allows frozen CookbookVersion
+ # documents on the server to be overwritten (otherwise a 409 is
+ # returned by the server)
+ def initialize(cookbook, path, opts={})
+ @cookbook, @path, @opts = cookbook, path, opts
end
def upload_cookbook
@@ -53,6 +65,7 @@ class Chef
)
headers = { 'content-type' => 'application/x-binary', 'content-md5' => checksum64, :accept => 'application/json' }
headers.merge!(sign_obj.sign(OpenSSL::PKey::RSA.new(rest.signing_key)))
+
begin
RestClient::Resource.new(info['url'], :headers=>headers, :timeout=>1800, :open_timeout=>1800).put(file_contents)
rescue RestClient::Exception => e
@@ -79,7 +92,7 @@ class Chef
end
end
# files are uploaded, so save the manifest
- cookbook.save
+ opts[:force] ? cookbook.force_save : cookbook.save
Chef::Log.info("Upload complete!")
end
diff --git a/chef/lib/chef/cookbook_version.rb b/chef/lib/chef/cookbook_version.rb
index 38f9c323d4..f25e0e5abb 100644
--- a/chef/lib/chef/cookbook_version.rb
+++ b/chef/lib/chef/cookbook_version.rb
@@ -339,6 +339,7 @@ class Chef
# object<Chef::CookbookVersion>:: Duh. :)
def initialize(name, couchdb=nil)
@name = name
+ @frozen = false
@attribute_filenames = Array.new
@definition_filenames = Array.new
@template_filenames = Array.new
@@ -364,6 +365,17 @@ class Chef
metadata.version
end
+ # Indicates if this version is frozen or not. Freezing a coobkook version
+ # indicates that a new cookbook with the same name and version number
+ # shoule
+ def frozen_version?
+ @frozen
+ end
+
+ def freeze_version
+ @frozen = true
+ end
+
def version=(new_version)
manifest["version"] = new_version
metadata.version(new_version)
@@ -650,6 +662,7 @@ class Chef
def to_hash
result = manifest.dup
+ result['frozen?'] = frozen_version?
result['chef_type'] = 'cookbook_version'
result["_rev"] = couchdb_rev if couchdb_rev
result.to_hash
@@ -675,6 +688,7 @@ class Chef
cookbook_version.manifest = o
# We want the Chef::Cookbook::Metadata object to always be inflated
cookbook_version.metadata = Chef::Cookbook::Metadata.from_hash(o["metadata"])
+ cookbook_version.freeze_version if o["frozen?"]
cookbook_version
end
@@ -716,12 +730,23 @@ class Chef
self.class.chef_server_rest
end
+ # Save this object to the server via the REST api. If there is an existing
+ # document on the server and it is marked frozen, a
+ # Net::HTTPServerException will be raised for 409 Conflict.
def save
chef_server_rest.put_rest("cookbooks/#{name}/#{version}", self)
self
end
alias :create :save
+ # Adds the `force=true` parameter to the upload. This allows the user to
+ # overwrite a frozen cookbook (normal #save raises a
+ # Net::HTTPServerException for 409 Conflict in this case).
+ def force_save
+ chef_server_rest.put_rest("cookbooks/#{name}/#{version}?force=true", self)
+ self
+ end
+
def destroy
chef_server_rest.delete_rest("cookbooks/#{name}/#{version}")
self
diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb
index 2cdccbe0b1..58091e34c8 100644
--- a/chef/lib/chef/exceptions.rb
+++ b/chef/lib/chef/exceptions.rb
@@ -50,6 +50,9 @@ class Chef
class RedirectLimitExceeded < RuntimeError; end
class AmbiguousRunlistSpecification < ArgumentError; end
class CookbookNotFound < RuntimeError; end
+ # Cookbook loader used to raise an argument error when cookbook not found.
+ # for back compat, need to raise an error that inherits from ArgumentError
+ class CookbookNotFoundInRepo < ArgumentError; end
class AttributeNotFound < RuntimeError; end
class InvalidCommandOption < RuntimeError; end
class CommandTimeout < RuntimeError; end
diff --git a/chef/lib/chef/knife/cookbook_upload.rb b/chef/lib/chef/knife/cookbook_upload.rb
index a2c4b971db..d6f7b3a166 100644
--- a/chef/lib/chef/knife/cookbook_upload.rb
+++ b/chef/lib/chef/knife/cookbook_upload.rb
@@ -17,6 +17,7 @@
# limitations under the License.
#
+require 'chef/exceptions'
require 'chef/knife'
require 'chef/cookbook_loader'
require 'chef/cookbook_uploader'
@@ -34,57 +35,110 @@ class Chef
:description => "A colon-separated path to look for cookbooks in",
:proc => lambda { |o| o.split(":") }
+ option :freeze,
+ :long => '--freeze',
+ :description => 'Freeze this version of the cookbook so that it cannot be overwritten',
+ :boolean => true
+
option :all,
:short => "-a",
:long => "--all",
:description => "Upload all cookbooks, rather than just a single cookbook"
+ option :force,
+ :long => '--force',
+ :boolean => true,
+ :description => "Update cookbook versions even if they have been frozen"
+
+ option :environment,
+ :short => '-E',
+ :long => '--environment ENVIRONMENT',
+ :description => "Set ENVIRONMENT's version dependency match the version you're uploading."
+
def run
config[:cookbook_path] ||= Chef::Config[:cookbook_path]
- Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, config[:cookbook_path]) }
+ assert_environment_valid! if config[:environment]
+ version_constraints_to_update = {}
- cl = Chef::CookbookLoader.new(config[:cookbook_path])
-
- humanize_auth_exceptions do
- if config[:all]
- cl.each do |cookbook_name, cookbook|
- Chef::Log.info("** #{cookbook.name.to_s} **")
- Chef::CookbookUploader.new(cookbook, config[:cookbook_path]).upload_cookbook
- end
- else
- if @name_args.length < 1
- show_usage
- Chef::Log.fatal("You must specify the --all flag or at least one cookbook name")
- exit 1
- end
- @name_args.each do |cookbook_name|
- if cl.cookbook_exists?(cookbook_name)
- Chef::CookbookUploader.new(cl[cookbook_name], config[:cookbook_path]).upload_cookbook
- else
- Chef::Log.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
- end
+ if config[:all]
+ cookbook_repo.each do |cookbook_name, cookbook|
+ cookbook.freeze_version if config[:freeze]
+ upload(cookbook)
+ version_constraints_to_update[cookbook_name] = cookbook.version
+ end
+ else
+ if @name_args.empty?
+ show_usage
+ Chef::Log.fatal("You must specify the --all flag or at least one cookbook name")
+ exit 1
+ end
+ @name_args.each do |cookbook_name|
+ begin
+ cookbook = cookbook_repo[cookbook_name]
+ cookbook.freeze_version if config[:freeze]
+ upload(cookbook)
+ version_constraints_to_update[cookbook_name] = cookbook.version
+ rescue Exceptions::CookbookNotFoundInRepo => e
+ Log.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it")
+ Log.debug(e)
end
end
end
+
+ update_version_constraints(version_constraints_to_update) if config[:environment]
+ end
+
+ def cookbook_repo
+ @cookbook_loader ||= begin
+ Chef::Cookbook::FileVendor.on_create { |manifest| Chef::Cookbook::FileSystemFileVendor.new(manifest, config[:cookbook_path]) }
+ Chef::CookbookLoader.new(config[:cookbook_path])
+ end
+ end
+
+ def update_version_constraints(new_version_constraints)
+ new_version_constraints.each do |cookbook_name, version|
+ environment.cookbook_versions[cookbook_name] = "= #{version}"
+ end
+ environment.save
+ end
+
+
+ def environment
+ @environment ||= Environment.load(config[:environment])
end
private
- def humanize_auth_exceptions
- begin
- yield
- rescue Net::HTTPServerException => e
- case e.response.code
- when "401"
- Chef::Log.fatal "Request failed due to authentication (#{e}), check your client configuration (username, key)"
- exit 18
- else
- raise
- end
+ def assert_environment_valid!
+ environment
+ rescue Net::HTTPServerException => e
+ if e.response.code.to_s == "404"
+ Log.error "The environment #{config[:environment]} does not exist on the server"
+ Log.debug(e)
+ exit 1
+ else
+ raise
end
end
+ def upload(cookbook)
+ Chef::Log.info("** #{cookbook.name} **")
+ Chef::CookbookUploader.new(cookbook, config[:cookbook_path], :force => config[:force]).upload_cookbook
+ rescue Net::HTTPServerException => e
+ case e.response.code
+ when "401"
+ # The server has good messages for 401s now, so use them:
+ Log.error Array(Chef::JSONCompat.from_json(e.response.body)["error"]).first
+ Log.debug(e)
+ exit 18
+ when "409"
+ Log.error "Version #{cookbook.version} of cookbook #{cookbook.name} is frozen. Use --force to override."
+ Log.debug(e)
+ else
+ raise
+ end
+ end
end
end
diff --git a/chef/spec/unit/cookbook_loader_spec.rb b/chef/spec/unit/cookbook_loader_spec.rb
index 97c1ccab8f..08e833fa71 100644
--- a/chef/spec/unit/cookbook_loader_spec.rb
+++ b/chef/spec/unit/cookbook_loader_spec.rb
@@ -32,7 +32,7 @@ describe Chef::CookbookLoader do
it "should raise an exception if it cannot find a cookbook with []" do
- lambda { @cookbook_loader[:monkeypoop] }.should raise_error(ArgumentError)
+ lambda { @cookbook_loader[:monkeypoop] }.should raise_error(Chef::Exceptions::CookbookNotFoundInRepo)
end
it "should allow you to look up available cookbooks with [] and a symbol" do
diff --git a/chef/spec/unit/cookbook_version_spec.rb b/chef/spec/unit/cookbook_version_spec.rb
index 772fab3061..0f37c66566 100644
--- a/chef/spec/unit/cookbook_version_spec.rb
+++ b/chef/spec/unit/cookbook_version_spec.rb
@@ -60,6 +60,15 @@ describe Chef::CookbookVersion do
@cookbook_version.metadata_filenames.should be_empty
end
+ it "is not frozen" do
+ @cookbook_version.should_not be_frozen_version
+ end
+
+ it "can be frozen" do
+ @cookbook_version.freeze_version
+ @cookbook_version.should be_frozen_version
+ end
+
it "has no couchdb id" do
@cookbook_version.couchdb_id.should be_nil
end