# # Author:: Adam Jacob () # Author:: Christopher Walters () # Copyright:: Copyright (c) 2009, 2010 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 'chef/knife' class Chef class Knife class CookbookUpload < Knife CHECKSUM = "checksum" MATCH_CHECKSUM = /[0-9a-f]{32,}/ deps do require 'chef/exceptions' require 'chef/cookbook_loader' require 'chef/cookbook_uploader' end banner "knife cookbook upload [COOKBOOKS...] (options)" option :cookbook_path, :short => "-o PATH:PATH", :long => "--cookbook-path PATH:PATH", :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.", :default => nil def run config[:cookbook_path] ||= Chef::Config[:cookbook_path] assert_environment_valid! version_constraints_to_update = {} if config[:all] justify_width = cookbook_repo.cookbook_names.map {|name| name.size}.max.to_i + 2 cookbook_repo.each do |cookbook_name, cookbook| cookbook.freeze_version if config[:freeze] upload(cookbook, justify_width) version_constraints_to_update[cookbook_name] = cookbook.version end else if @name_args.empty? show_usage ui.error("You must specify the --all flag or at least one cookbook name") exit 1 end justify_width = @name_args.map {|name| name.size }.max.to_i + 2 @name_args.each do |cookbook_name| begin cookbook = cookbook_repo[cookbook_name] cookbook.freeze_version if config[:freeze] upload(cookbook, justify_width) version_constraints_to_update[cookbook_name] = cookbook.version rescue Exceptions::CookbookNotFoundInRepo => e ui.error("Could not find cookbook #{cookbook_name} in your cookbook path, skipping it") Log.debug(e) end end end ui.info "upload complete" 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 ||= config[:environment] ? Environment.load(config[:environment]) : nil end private def assert_environment_valid! environment rescue Net::HTTPServerException => e if e.response.code.to_s == "404" ui.error "The environment #{config[:environment]} does not exist on the server, aborting." Log.debug(e) exit 1 else raise end end def upload(cookbook, justify_width) ui.info("Uploading #{cookbook.name.to_s.ljust(justify_width + 10)} [#{cookbook.version}]") check_for_broken_links(cookbook) Chef::CookbookUploader.new(cookbook, config[:cookbook_path], :force => config[:force]).upload_cookbook rescue Net::HTTPServerException => e case e.response.code when "409" ui.error "Version #{cookbook.version} of cookbook #{cookbook.name} is frozen. Use --force to override." Log.debug(e) else raise end end # if only you people wouldn't put broken symlinks in your cookbooks in # the first place. ;) def check_for_broken_links(cookbook) # MUST!! dup the cookbook version object--it memoizes its # manifest object, but the manifest becomes invalid when you # regenerate the metadata broken_files = cookbook.dup.manifest_records_by_path.select do |path, info| info[CHECKSUM].nil? || info[CHECKSUM] !~ MATCH_CHECKSUM end unless broken_files.empty? broken_filenames = Array(broken_files).map {|path, info| path} ui.error "The cookbook #{cookbook.name} has one or more broken files" ui.info "This is probably caused by broken symlinks in the cookbook directory" ui.info "The broken file(s) are: #{broken_filenames.join(' ')}" exit 1 end end end end end