class ChefCLI::Policyfile::Uploader
def compat_mode_cookbook_exists_on_remote?(cookbook)
def compat_mode_cookbook_exists_on_remote?(cookbook) existing_cookbook_on_remote[cookbook.name.to_s]["versions"].any? do |cookbook_info| cookbook_info["version"] == cookbook.version end end
def cookbook_versions_for_policy
An Array of Chef::CookbookVersion objects representing the full set that
def cookbook_versions_for_policy return @cookbook_versions_for_policy if @cookbook_versions_for_policy policyfile_lock.validate_cookbooks! @cookbook_versions_for_policy = if using_policy_document_native_api? load_cookbooks_in_native_mode else load_cookbooks_in_compat_mode end end
def cookbook_versions_to_upload
def cookbook_versions_to_upload cookbook_versions_for_policy.inject([]) do |versions_to_upload, cookbook_with_lock| cb = cookbook_with_lock.cookbook # When we abandon custom identifier support in favor of the one true # hash, identifier generation code can be moved into chef proper and # this can be removed. cb.identifier = cookbook_with_lock.lock.identifier versions_to_upload << cb unless remote_already_has_cookbook?(cb) versions_to_upload end end
def data_bag_create
def data_bag_create http_client.post("data", { "name" => COMPAT_MODE_DATA_BAG_NAME }) rescue Net::HTTPClientException => e raise e unless e.response.code == "409" end
def data_bag_item_create
def data_bag_item_create policy_id = "#{policy_name}-#{policy_group}" lock_data = policy_lock_for_transport.dup lock_data["id"] = policy_id data_item = { "id" => policy_id, "name" => "data_bag_item_#{COMPAT_MODE_DATA_BAG_NAME}_#{policy_id}", "data_bag" => COMPAT_MODE_DATA_BAG_NAME, "raw_data" => lock_data, # we'd prefer to leave this out, but the "compatibility mode" # implementation in chef-client relies on magical class inflation "json_class" => "Chef::DataBagItem", } upload_lockfile_as_data_bag_item(policy_id, data_item) ui.msg("Policy uploaded as data bag item #{COMPAT_MODE_DATA_BAG_NAME}/#{policy_id}") true end
def existing_cookbook_on_remote
def existing_cookbook_on_remote @existing_cookbook_on_remote ||= http_client.get(list_cookbooks_url) end
def initialize(policyfile_lock, policy_group, ui: nil, http_client: nil, policy_document_native_api: false)
def initialize(policyfile_lock, policy_group, ui: nil, http_client: nil, policy_document_native_api: false) @policyfile_lock = policyfile_lock @policy_group = policy_group @http_client = http_client @ui = ui || UI.null @policy_document_native_api = policy_document_native_api @policy_lock_for_transport = nil @cookbook_versions_for_policy = nil end
def list_cookbooks_url
def list_cookbooks_url if using_policy_document_native_api? "cookbook_artifacts?num_versions=all" else "cookbooks?num_versions=all" end end
def load_cookbooks_in_compat_mode
def load_cookbooks_in_compat_mode policyfile_lock.cookbook_locks.map do |name, lock| cb = ReadCookbookForCompatModeUpload.load(name, lock.dotted_decimal_identifier, lock.cookbook_path) LockedCookbookForUpload.new(cb, lock) end end
def load_cookbooks_in_native_mode
def load_cookbooks_in_native_mode policyfile_lock.cookbook_locks.map do |name, lock| cb = CookbookLoaderWithChefignore.load(name, lock.cookbook_path) LockedCookbookForUpload.new(cb, lock) end end
def native_mode_cookbook_exists_on_remote?(cookbook)
def native_mode_cookbook_exists_on_remote?(cookbook) existing_cookbook_on_remote[cookbook.name.to_s]["versions"].any? do |cookbook_info| cookbook_info["identifier"] == cookbook.identifier end end
def policy_lock_for_transport
def policy_lock_for_transport @policy_lock_for_transport ||= policyfile_lock.to_lock end
def policy_name
def policy_name policyfile_lock.name end
def remote_already_has_cookbook?(cookbook)
def remote_already_has_cookbook?(cookbook) return false unless existing_cookbook_on_remote.key?(cookbook.name.to_s) if using_policy_document_native_api? native_mode_cookbook_exists_on_remote?(cookbook) else compat_mode_cookbook_exists_on_remote?(cookbook) end end
def short_revision_id
def short_revision_id policy_lock_for_transport["revision_id"][0, 10] end
def upload
def upload ui.msg("Uploading policy #{policy_name} (#{short_revision_id}) to policy group #{policy_group}") unless using_policy_document_native_api? ui.msg(<<~DRAGONS) WARN: Uploading policy to policy group #{policy_group} in compatibility mode. Cookbooks will be uploaded with very large version numbers, which may be picked up by existing nodes. DRAGONS end upload_cookbooks upload_policy end
def upload_cookbooks
def upload_cookbooks ui.msg("WARN: Uploading cookbooks using semver compat mode") unless using_policy_document_native_api? uploader.upload_cookbooks unless cookbook_versions_to_upload.empty? reused_cbs, uploaded_cbs = cookbook_versions_for_policy.partition do |cb_with_lock| remote_already_has_cookbook?(cb_with_lock.cookbook) end Reports::Upload.new(reused_cbs: reused_cbs, uploaded_cbs: uploaded_cbs, ui: ui).show true end
def upload_lockfile_as_data_bag_item(policy_id, data_item)
def upload_lockfile_as_data_bag_item(policy_id, data_item) http_client.put("data/#{COMPAT_MODE_DATA_BAG_NAME}/#{policy_id}", data_item) rescue Net::HTTPClientException => e raise e unless e.response.code == "404" http_client.post("data/#{COMPAT_MODE_DATA_BAG_NAME}", data_item) end
def upload_policy
def upload_policy if using_policy_document_native_api? upload_policy_native else data_bag_create data_bag_item_create end end
def upload_policy_native
def upload_policy_native http_client.put("/policy_groups/#{policy_group}/policies/#{policy_name}", policy_lock_for_transport) end
def uploader
def uploader # TODO: uploader runs cookbook validation; we want to do this at a different time. @uploader ||= Chef::CookbookUploader.new(cookbook_versions_to_upload, rest: http_client, policy_mode: using_policy_document_native_api?) end
def using_policy_document_native_api?
def using_policy_document_native_api? @policy_document_native_api end